144 lines
3.4 KiB
TypeScript
144 lines
3.4 KiB
TypeScript
import type { ExportQuality } from "./types";
|
|
|
|
export interface Mp4ExportSettings {
|
|
width: number;
|
|
height: number;
|
|
bitrate: number;
|
|
}
|
|
|
|
interface SourceCropRegion {
|
|
width: number;
|
|
height: number;
|
|
}
|
|
|
|
const MEDIUM_SHORT_SIDE = 720;
|
|
const HIGH_SHORT_SIDE = 1080;
|
|
|
|
function even(value: number) {
|
|
return Math.floor(value / 2) * 2;
|
|
}
|
|
|
|
function atLeastEven(value: number) {
|
|
return Math.max(2, even(value));
|
|
}
|
|
|
|
export function calculateEffectiveSourceDimensions(
|
|
sourceWidth: number,
|
|
sourceHeight: number,
|
|
cropRegion?: SourceCropRegion,
|
|
) {
|
|
const cropWidth = cropRegion?.width ?? 1;
|
|
const cropHeight = cropRegion?.height ?? 1;
|
|
|
|
return {
|
|
width: atLeastEven(Math.round(sourceWidth * cropWidth)),
|
|
height: atLeastEven(Math.round(sourceHeight * cropHeight)),
|
|
};
|
|
}
|
|
|
|
function calculateDimensionsForShortSide(targetShortSide: number, aspectRatioValue: number) {
|
|
if (aspectRatioValue >= 1) {
|
|
const height = even(targetShortSide);
|
|
return {
|
|
width: even(height * aspectRatioValue),
|
|
height,
|
|
};
|
|
}
|
|
|
|
const width = even(targetShortSide);
|
|
return {
|
|
width,
|
|
height: even(width / aspectRatioValue),
|
|
};
|
|
}
|
|
|
|
function calculateSourceDimensions(
|
|
sourceWidth: number,
|
|
sourceHeight: number,
|
|
aspectRatioValue: number,
|
|
) {
|
|
const sourceLongDim = Math.max(sourceWidth, sourceHeight);
|
|
|
|
if (aspectRatioValue === 1) {
|
|
const baseDimension = even(Math.min(sourceWidth, sourceHeight));
|
|
return {
|
|
width: baseDimension,
|
|
height: baseDimension,
|
|
};
|
|
}
|
|
|
|
if (aspectRatioValue > 1) {
|
|
const baseWidth = even(sourceLongDim);
|
|
for (let width = baseWidth; width >= 100; width -= 2) {
|
|
const height = Math.round(width / aspectRatioValue);
|
|
if (height % 2 === 0 && Math.abs(width / height - aspectRatioValue) < 0.0001) {
|
|
return { width, height };
|
|
}
|
|
}
|
|
return {
|
|
width: baseWidth,
|
|
height: even(baseWidth / aspectRatioValue),
|
|
};
|
|
}
|
|
|
|
const baseHeight = even(sourceLongDim);
|
|
for (let height = baseHeight; height >= 100; height -= 2) {
|
|
const width = Math.round(height * aspectRatioValue);
|
|
if (width % 2 === 0 && Math.abs(width / height - aspectRatioValue) < 0.0001) {
|
|
return { width, height };
|
|
}
|
|
}
|
|
return {
|
|
width: even(baseHeight * aspectRatioValue),
|
|
height: baseHeight,
|
|
};
|
|
}
|
|
|
|
function calculateBitrate(width: number, height: number, quality: ExportQuality) {
|
|
const totalPixels = width * height;
|
|
|
|
if (quality === "source") {
|
|
if (totalPixels > 2560 * 1440) return 80_000_000;
|
|
if (totalPixels > 1920 * 1080) return 50_000_000;
|
|
return 30_000_000;
|
|
}
|
|
|
|
if (totalPixels <= 1280 * 720) return 10_000_000;
|
|
if (totalPixels <= 1920 * 1080) return 20_000_000;
|
|
return 30_000_000;
|
|
}
|
|
|
|
export function calculateMp4ExportSettings({
|
|
quality,
|
|
sourceWidth,
|
|
sourceHeight,
|
|
aspectRatioValue,
|
|
}: {
|
|
quality: ExportQuality;
|
|
sourceWidth: number;
|
|
sourceHeight: number;
|
|
aspectRatioValue: number;
|
|
}): Mp4ExportSettings {
|
|
if (quality === "medium") {
|
|
const dimensions = calculateDimensionsForShortSide(MEDIUM_SHORT_SIDE, aspectRatioValue);
|
|
return {
|
|
...dimensions,
|
|
bitrate: calculateBitrate(dimensions.width, dimensions.height, quality),
|
|
};
|
|
}
|
|
|
|
if (quality === "good") {
|
|
const dimensions = calculateDimensionsForShortSide(HIGH_SHORT_SIDE, aspectRatioValue);
|
|
return {
|
|
...dimensions,
|
|
bitrate: calculateBitrate(dimensions.width, dimensions.height, quality),
|
|
};
|
|
}
|
|
|
|
const sourceDimensions = calculateSourceDimensions(sourceWidth, sourceHeight, aspectRatioValue);
|
|
return {
|
|
...sourceDimensions,
|
|
bitrate: calculateBitrate(sourceDimensions.width, sourceDimensions.height, quality),
|
|
};
|
|
}
|