2023-08-14 11:26:31 +02:00

129 lines
3.6 KiB
TypeScript

import type { ApiAttachment } from '../../../../api/types';
import {
GIF_MIME_TYPE,
SUPPORTED_AUDIO_CONTENT_TYPES,
SUPPORTED_IMAGE_CONTENT_TYPES,
SUPPORTED_VIDEO_CONTENT_TYPES,
} from '../../../../config';
import { parseAudioMetadata } from '../../../../util/audio';
import {
preloadImage,
preloadVideo,
createPosterForVideo,
} from '../../../../util/files';
import { scaleImage } from '../../../../util/imageResize';
const MAX_QUICK_IMG_SIZE = 1280; // px
const MAX_THUMB_IMG_SIZE = 40; // px
const MAX_ASPECT_RATIO = 20;
const FILE_EXT_REGEX = /\.[^/.]+$/;
export default async function buildAttachment(
filename: string, blob: Blob, options?: Partial<ApiAttachment>,
): Promise<ApiAttachment> {
const blobUrl = URL.createObjectURL(blob);
const { type: mimeType, size } = blob;
let quick;
let audio;
let previewBlobUrl;
let shouldSendAsFile;
if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType)) {
const img = await preloadImage(blobUrl);
const { width, height } = img;
shouldSendAsFile = !validateAspectRatio(width, height);
const shouldShrink = Math.max(width, height) > MAX_QUICK_IMG_SIZE;
const isGif = mimeType === GIF_MIME_TYPE;
if (!shouldSendAsFile) {
if (!options?.compressedBlobUrl && !isGif && (shouldShrink || mimeType !== 'image/jpeg')) {
const resizedUrl = await scaleImage(
blobUrl, shouldShrink ? MAX_QUICK_IMG_SIZE / Math.max(width, height) : 1, 'image/jpeg',
);
URL.revokeObjectURL(blobUrl);
return buildAttachment(filename, blob, {
compressedBlobUrl: resizedUrl,
});
}
if (mimeType === 'image/jpeg') {
filename = filename.replace(FILE_EXT_REGEX, '.jpg');
}
quick = { width, height };
}
const shouldShrinkPreview = Math.max(width, height) > MAX_THUMB_IMG_SIZE;
if (shouldShrinkPreview) {
previewBlobUrl = await scaleImage(
blobUrl, MAX_THUMB_IMG_SIZE / Math.max(width, height), 'image/jpeg',
);
} else {
previewBlobUrl = blobUrl;
}
} else if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) {
try {
const { videoWidth: width, videoHeight: height, duration } = await preloadVideo(blobUrl);
shouldSendAsFile = !validateAspectRatio(width, height);
if (!shouldSendAsFile) {
quick = { width: width!, height: height!, duration: duration! };
}
} catch (err) {
shouldSendAsFile = true;
}
previewBlobUrl = await createPosterForVideo(blobUrl);
} else if (SUPPORTED_AUDIO_CONTENT_TYPES.has(mimeType)) {
const {
duration, title, performer, coverUrl,
} = await parseAudioMetadata(blobUrl);
audio = {
duration: duration || 0,
title,
performer,
};
previewBlobUrl = coverUrl;
}
return {
blobUrl,
filename,
mimeType,
size,
quick,
audio,
previewBlobUrl,
shouldSendAsFile: shouldSendAsFile || undefined,
uniqueId: `${Date.now()}-${Math.random()}`,
...options,
};
}
export function prepareAttachmentsToSend(
attachments: ApiAttachment[], shouldSendCompressed?: boolean,
): ApiAttachment[] {
return attachments.map((attach) => {
if (shouldSendCompressed) {
if (attach.compressedBlobUrl) {
return {
...attach,
blobUrl: attach.compressedBlobUrl,
};
}
return attach;
}
return {
...attach,
shouldSendAsFile: !attach.voice ? true : undefined,
shouldSendAsSpoiler: undefined,
};
});
}
function validateAspectRatio(width: number, height: number) {
const maxAspectRatio = Math.max(width, height) / Math.min(width, height);
return maxAspectRatio <= MAX_ASPECT_RATIO;
}