2024-06-29 08:10:02 +02:00
|
|
|
/**
|
|
|
|
|
* @packageDocumentation
|
|
|
|
|
* @module MediaManager/Preprocessors
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import sharp from "sharp";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Supported input media formats.
|
|
|
|
|
*/
|
|
|
|
|
const supportedInputFormats = [
|
|
|
|
|
"image/png",
|
|
|
|
|
"image/jpeg",
|
|
|
|
|
"image/webp",
|
|
|
|
|
"image/avif",
|
|
|
|
|
"image/svg+xml",
|
|
|
|
|
"image/gif",
|
|
|
|
|
"image/tiff",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Supported output media formats.
|
|
|
|
|
*/
|
|
|
|
|
const supportedOutputFormats = [
|
|
|
|
|
"image/jpeg",
|
|
|
|
|
"image/png",
|
|
|
|
|
"image/webp",
|
|
|
|
|
"image/avif",
|
|
|
|
|
"image/gif",
|
|
|
|
|
"image/tiff",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/**
|
2025-01-29 17:21:40 +01:00
|
|
|
* Checks if a file is convertible.
|
|
|
|
|
* @param file - The file to check.
|
|
|
|
|
* @returns True if the file is convertible, false otherwise.
|
2024-06-29 08:10:02 +02:00
|
|
|
*/
|
2025-03-23 04:12:28 +01:00
|
|
|
const isConvertible = (
|
|
|
|
|
file: File,
|
|
|
|
|
options?: { convertVectors?: boolean },
|
|
|
|
|
): boolean => {
|
|
|
|
|
if (file.type === "image/svg+xml" && !options?.convertVectors) {
|
2025-01-29 17:21:40 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return supportedInputFormats.includes(file.type);
|
|
|
|
|
};
|
2024-06-29 08:10:02 +02:00
|
|
|
|
2025-01-29 17:21:40 +01:00
|
|
|
/**
|
|
|
|
|
* Extracts the filename from a path.
|
|
|
|
|
* @param path - The path to extract the filename from.
|
|
|
|
|
* @returns The extracted filename.
|
|
|
|
|
*/
|
|
|
|
|
const extractFilenameFromPath = (path: string): string => {
|
|
|
|
|
const pathParts = path.split(/(?<!\\)\//);
|
2025-04-10 19:56:42 +02:00
|
|
|
return pathParts.at(-1) as string;
|
2025-01-29 17:21:40 +01:00
|
|
|
};
|
2024-06-29 08:10:02 +02:00
|
|
|
|
2025-01-29 17:21:40 +01:00
|
|
|
/**
|
|
|
|
|
* Replaces the file extension in the filename.
|
|
|
|
|
* @param fileName - The original filename.
|
|
|
|
|
* @param newExtension - The new extension.
|
|
|
|
|
* @returns The filename with the new extension.
|
|
|
|
|
*/
|
|
|
|
|
const getReplacedFileName = (fileName: string, newExtension: string): string =>
|
|
|
|
|
extractFilenameFromPath(fileName).replace(/\.[^/.]+$/, `.${newExtension}`);
|
2024-06-29 08:10:02 +02:00
|
|
|
|
2025-01-29 17:21:40 +01:00
|
|
|
/**
|
|
|
|
|
* Converts an image file to the format specified in the configuration.
|
|
|
|
|
*
|
|
|
|
|
* @param file - The image file to convert.
|
2025-03-23 04:12:28 +01:00
|
|
|
* @param targetFormat - The target format to convert to.
|
2025-01-29 17:21:40 +01:00
|
|
|
* @returns The converted image file.
|
|
|
|
|
*/
|
2025-03-23 04:12:28 +01:00
|
|
|
export const convertImage = async (
|
|
|
|
|
file: File,
|
|
|
|
|
targetFormat: string,
|
|
|
|
|
options?: {
|
|
|
|
|
convertVectors?: boolean;
|
|
|
|
|
},
|
|
|
|
|
): Promise<File> => {
|
|
|
|
|
if (!isConvertible(file, options)) {
|
2025-01-29 17:21:40 +01:00
|
|
|
return file;
|
2024-06-29 08:10:02 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-29 17:21:40 +01:00
|
|
|
if (!supportedOutputFormats.includes(targetFormat)) {
|
|
|
|
|
throw new Error(`Unsupported output format: ${targetFormat}`);
|
2024-06-29 08:10:02 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-29 17:21:40 +01:00
|
|
|
const sharpCommand = sharp(await file.arrayBuffer(), {
|
|
|
|
|
animated: true,
|
|
|
|
|
});
|
|
|
|
|
const commandName = targetFormat.split("/")[1] as
|
|
|
|
|
| "jpeg"
|
|
|
|
|
| "png"
|
|
|
|
|
| "webp"
|
|
|
|
|
| "avif"
|
|
|
|
|
| "gif"
|
|
|
|
|
| "tiff";
|
|
|
|
|
const convertedBuffer = await sharpCommand[commandName]().toBuffer();
|
2024-06-29 08:10:02 +02:00
|
|
|
|
2025-01-29 17:21:40 +01:00
|
|
|
return new File(
|
2025-11-21 08:31:02 +01:00
|
|
|
[convertedBuffer as BlobPart],
|
2025-01-29 17:21:40 +01:00
|
|
|
getReplacedFileName(file.name, commandName),
|
|
|
|
|
{
|
|
|
|
|
type: targetFormat,
|
|
|
|
|
lastModified: Date.now(),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
};
|