2024-03-09 00:14:45 +01:00
|
|
|
/**
|
|
|
|
|
* @packageDocumentation
|
|
|
|
|
* @module MediaManager
|
|
|
|
|
* @description Handles media conversion between formats
|
|
|
|
|
*/
|
|
|
|
|
import sharp from "sharp";
|
|
|
|
|
|
|
|
|
|
export enum ConvertableMediaFormats {
|
2024-04-07 07:30:49 +02:00
|
|
|
PNG = "png",
|
|
|
|
|
WEBP = "webp",
|
|
|
|
|
JPEG = "jpeg",
|
|
|
|
|
JPG = "jpg",
|
|
|
|
|
AVIF = "avif",
|
|
|
|
|
JXL = "jxl",
|
|
|
|
|
HEIF = "heif",
|
2024-03-09 00:14:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handles media conversion between formats
|
|
|
|
|
*/
|
|
|
|
|
export class MediaConverter {
|
2024-04-07 07:30:49 +02:00
|
|
|
constructor(
|
|
|
|
|
public fromFormat: ConvertableMediaFormats,
|
|
|
|
|
public toFormat: ConvertableMediaFormats,
|
|
|
|
|
) {}
|
2024-03-09 00:14:45 +01:00
|
|
|
|
2024-04-07 07:30:49 +02:00
|
|
|
/**
|
|
|
|
|
* Returns whether the media is convertable
|
|
|
|
|
* @returns Whether the media is convertable
|
|
|
|
|
*/
|
|
|
|
|
public isConvertable() {
|
|
|
|
|
return (
|
|
|
|
|
this.fromFormat !== this.toFormat &&
|
|
|
|
|
Object.values(ConvertableMediaFormats).includes(this.fromFormat)
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-03-09 00:14:45 +01:00
|
|
|
|
2024-04-07 07:30:49 +02:00
|
|
|
/**
|
|
|
|
|
* Returns the file name with the extension replaced
|
|
|
|
|
* @param fileName File name to replace
|
|
|
|
|
* @returns File name with extension replaced
|
|
|
|
|
*/
|
|
|
|
|
private getReplacedFileName(fileName: string) {
|
|
|
|
|
return this.extractFilenameFromPath(fileName).replace(
|
|
|
|
|
new RegExp(`\\.${this.fromFormat}$`),
|
|
|
|
|
`.${this.toFormat}`,
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-03-09 00:14:45 +01:00
|
|
|
|
2024-04-07 07:30:49 +02:00
|
|
|
/**
|
|
|
|
|
* 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];
|
|
|
|
|
}
|
2024-03-09 00:14:45 +01:00
|
|
|
|
2024-04-07 07:30:49 +02:00
|
|
|
/**
|
|
|
|
|
* Converts media to the specified format
|
|
|
|
|
* @param media Media to convert
|
|
|
|
|
* @returns Converted media
|
|
|
|
|
*/
|
|
|
|
|
public async convert(media: File) {
|
|
|
|
|
if (!this.isConvertable()) {
|
|
|
|
|
return media;
|
|
|
|
|
}
|
2024-03-09 00:14:45 +01:00
|
|
|
|
2024-04-07 07:30:49 +02:00
|
|
|
const sharpCommand = sharp(await media.arrayBuffer());
|
2024-03-09 00:14:45 +01:00
|
|
|
|
2024-04-07 07:30:49 +02:00
|
|
|
// Calculate newFilename before changing formats to prevent errors with jpg files
|
|
|
|
|
const newFilename = this.getReplacedFileName(media.name);
|
2024-03-09 00:14:45 +01:00
|
|
|
|
2024-04-07 07:30:49 +02:00
|
|
|
if (this.fromFormat === ConvertableMediaFormats.JPG) {
|
|
|
|
|
this.fromFormat = ConvertableMediaFormats.JPEG;
|
|
|
|
|
}
|
2024-03-09 00:14:45 +01:00
|
|
|
|
2024-04-07 07:30:49 +02:00
|
|
|
if (this.toFormat === ConvertableMediaFormats.JPG) {
|
|
|
|
|
this.toFormat = ConvertableMediaFormats.JPEG;
|
|
|
|
|
}
|
2024-03-09 00:14:45 +01:00
|
|
|
|
2024-04-07 07:30:49 +02:00
|
|
|
const convertedBuffer = await sharpCommand[this.toFormat]().toBuffer();
|
2024-03-09 00:14:45 +01:00
|
|
|
|
2024-04-07 07:30:49 +02:00
|
|
|
// Convert the buffer to a BlobPart
|
|
|
|
|
const buffer = new Blob([convertedBuffer]);
|
2024-03-09 00:14:45 +01:00
|
|
|
|
2024-04-07 07:30:49 +02:00
|
|
|
return new File([buffer], newFilename, {
|
|
|
|
|
type: `image/${this.toFormat}`,
|
|
|
|
|
lastModified: Date.now(),
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-03-09 00:14:45 +01:00
|
|
|
}
|