mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 16:38:19 +01:00
118 lines
2.9 KiB
TypeScript
118 lines
2.9 KiB
TypeScript
/**
|
|
* @packageDocumentation
|
|
* @module MediaManager
|
|
* @description Handles media conversion between formats
|
|
*/
|
|
import { config } from "config-manager";
|
|
import sharp from "sharp";
|
|
|
|
export const supportedMediaFormats = [
|
|
"image/png",
|
|
"image/jpeg",
|
|
"image/webp",
|
|
"image/avif",
|
|
"image/svg+xml",
|
|
"image/gif",
|
|
"image/tiff",
|
|
];
|
|
|
|
export const supportedOutputFormats = [
|
|
"image/jpeg",
|
|
"image/png",
|
|
"image/webp",
|
|
"image/avif",
|
|
"image/gif",
|
|
"image/tiff",
|
|
];
|
|
|
|
/**
|
|
* Handles media conversion between formats
|
|
*/
|
|
export class MediaConverter {
|
|
/**
|
|
* Returns whether the media is convertable
|
|
* @returns Whether the media is convertable
|
|
*/
|
|
public isConvertable(file: File) {
|
|
if (
|
|
file.type === "image/svg+xml" &&
|
|
!config.media.conversion.convert_vector
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return supportedMediaFormats.includes(file.type);
|
|
}
|
|
|
|
/**
|
|
* Returns the file name with the extension replaced
|
|
* @param fileName File name to replace
|
|
* @returns File name with extension replaced
|
|
*/
|
|
private getReplacedFileName(fileName: string, newExtension: string) {
|
|
return this.extractFilenameFromPath(fileName).replace(
|
|
/\.[^/.]+$/,
|
|
`.${newExtension}`,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Extracts the filename from a path
|
|
* @param path Path to extract filename from
|
|
* @returns Extracted filename
|
|
*/
|
|
private extractFilenameFromPath(path: string) {
|
|
// Don't count escaped slashes as path separators
|
|
const pathParts = path.split(/(?<!\\)\//);
|
|
return pathParts[pathParts.length - 1];
|
|
}
|
|
|
|
/**
|
|
* Converts media to the specified format
|
|
* @param media Media to convert
|
|
* @returns Converted media
|
|
*/
|
|
public async convert(
|
|
media: File,
|
|
toMime: (typeof supportedMediaFormats)[number],
|
|
) {
|
|
if (!this.isConvertable(media)) {
|
|
return media;
|
|
}
|
|
|
|
if (!supportedOutputFormats.includes(toMime)) {
|
|
throw new Error(
|
|
`Unsupported image output format: ${toMime}. Supported formats: ${supportedOutputFormats.join(
|
|
", ",
|
|
)}`,
|
|
);
|
|
}
|
|
|
|
const sharpCommand = sharp(await media.arrayBuffer(), {
|
|
animated: true,
|
|
});
|
|
|
|
const commandName = toMime.split("/")[1] as
|
|
| "jpeg"
|
|
| "png"
|
|
| "webp"
|
|
| "avif"
|
|
| "gif"
|
|
| "tiff";
|
|
|
|
const convertedBuffer = await sharpCommand[commandName]().toBuffer();
|
|
|
|
// Convert the buffer to a BlobPart
|
|
const buffer = new Blob([convertedBuffer]);
|
|
|
|
return new File(
|
|
[buffer],
|
|
this.getReplacedFileName(media.name || "image", commandName),
|
|
{
|
|
type: toMime,
|
|
lastModified: Date.now(),
|
|
},
|
|
);
|
|
}
|
|
}
|