mirror of
https://github.com/ente-io/ente.git
synced 2025-08-09 07:48:52 +00:00
Move back
This commit is contained in:
parent
72fa6c653f
commit
64572d5880
@ -1,10 +1,9 @@
|
|||||||
import { FILE_TYPE, type FileTypeInfo } from "@/media/file-type";
|
import { FILE_TYPE, type FileTypeInfo } from "@/media/file-type";
|
||||||
import {
|
import { scaledImageDimensions } from "@/media/image";
|
||||||
generateImageThumbnailUsingCanvas,
|
|
||||||
generateVideoThumbnailUsingCanvas,
|
|
||||||
} from "@/media/image";
|
|
||||||
import log from "@/next/log";
|
import log from "@/next/log";
|
||||||
import { type Electron } from "@/next/types/ipc";
|
import { type Electron } from "@/next/types/ipc";
|
||||||
|
import { ensure } from "@/utils/ensure";
|
||||||
|
import { withTimeout } from "@/utils/promise";
|
||||||
import * as ffmpeg from "services/ffmpeg";
|
import * as ffmpeg from "services/ffmpeg";
|
||||||
import { heicToJPEG } from "services/heic-convert";
|
import { heicToJPEG } from "services/heic-convert";
|
||||||
import { toDataOrPathOrZipEntry, type DesktopUploadItem } from "./types";
|
import { toDataOrPathOrZipEntry, type DesktopUploadItem } from "./types";
|
||||||
@ -48,6 +47,64 @@ const generateImageThumbnailWeb = async (
|
|||||||
return generateImageThumbnailUsingCanvas(blob);
|
return generateImageThumbnailUsingCanvas(blob);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const generateImageThumbnailUsingCanvas = async (blob: Blob) => {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
const canvasCtx = ensure(canvas.getContext("2d"));
|
||||||
|
|
||||||
|
const imageURL = URL.createObjectURL(blob);
|
||||||
|
await withTimeout(
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const image = new Image();
|
||||||
|
image.setAttribute("src", imageURL);
|
||||||
|
image.onload = () => {
|
||||||
|
try {
|
||||||
|
URL.revokeObjectURL(imageURL);
|
||||||
|
const { width, height } = scaledImageDimensions(
|
||||||
|
image.width,
|
||||||
|
image.height,
|
||||||
|
maxThumbnailDimension,
|
||||||
|
);
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
canvasCtx.drawImage(image, 0, 0, width, height);
|
||||||
|
resolve(undefined);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
30 * 1000,
|
||||||
|
);
|
||||||
|
|
||||||
|
return await compressedJPEGData(canvas);
|
||||||
|
};
|
||||||
|
|
||||||
|
const compressedJPEGData = async (canvas: HTMLCanvasElement) => {
|
||||||
|
let blob: Blob | undefined | null;
|
||||||
|
let prevSize = Number.MAX_SAFE_INTEGER;
|
||||||
|
let quality = 0.7;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (blob) prevSize = blob.size;
|
||||||
|
blob = await new Promise((resolve) => {
|
||||||
|
canvas.toBlob((blob) => resolve(blob), "image/jpeg", quality);
|
||||||
|
});
|
||||||
|
quality -= 0.1;
|
||||||
|
} while (
|
||||||
|
quality >= 0.5 &&
|
||||||
|
blob &&
|
||||||
|
blob.size > maxThumbnailSize &&
|
||||||
|
percentageSizeDiff(blob.size, prevSize) >= 10
|
||||||
|
);
|
||||||
|
|
||||||
|
return new Uint8Array(await ensure(blob).arrayBuffer());
|
||||||
|
};
|
||||||
|
|
||||||
|
const percentageSizeDiff = (
|
||||||
|
newThumbnailSize: number,
|
||||||
|
oldThumbnailSize: number,
|
||||||
|
) => ((oldThumbnailSize - newThumbnailSize) * 100) / oldThumbnailSize;
|
||||||
|
|
||||||
const generateVideoThumbnailWeb = async (blob: Blob) => {
|
const generateVideoThumbnailWeb = async (blob: Blob) => {
|
||||||
try {
|
try {
|
||||||
return await ffmpeg.generateVideoThumbnailWeb(blob);
|
return await ffmpeg.generateVideoThumbnailWeb(blob);
|
||||||
@ -60,6 +117,39 @@ const generateVideoThumbnailWeb = async (blob: Blob) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const generateVideoThumbnailUsingCanvas = async (blob: Blob) => {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
const canvasCtx = ensure(canvas.getContext("2d"));
|
||||||
|
|
||||||
|
const videoURL = URL.createObjectURL(blob);
|
||||||
|
await withTimeout(
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const video = document.createElement("video");
|
||||||
|
video.preload = "metadata";
|
||||||
|
video.src = videoURL;
|
||||||
|
video.addEventListener("loadeddata", () => {
|
||||||
|
try {
|
||||||
|
URL.revokeObjectURL(videoURL);
|
||||||
|
const { width, height } = scaledImageDimensions(
|
||||||
|
video.videoWidth,
|
||||||
|
video.videoHeight,
|
||||||
|
maxThumbnailDimension,
|
||||||
|
);
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
canvasCtx.drawImage(video, 0, 0, width, height);
|
||||||
|
resolve(undefined);
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
30 * 1000,
|
||||||
|
);
|
||||||
|
|
||||||
|
return await compressedJPEGData(canvas);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a JPEG thumbnail for the given file or path using native tools.
|
* Generate a JPEG thumbnail for the given file or path using native tools.
|
||||||
*
|
*
|
||||||
|
@ -1,128 +1,33 @@
|
|||||||
import { ensure } from "@/utils/ensure";
|
|
||||||
import { withTimeout } from "@/utils/promise";
|
|
||||||
|
|
||||||
/** Maximum width or height of the generated thumbnail */
|
|
||||||
const maxThumbnailDimension = 720;
|
|
||||||
/** Maximum size (in bytes) of the generated thumbnail */
|
|
||||||
const maxThumbnailSize = 100 * 1024; // 100 KB
|
|
||||||
|
|
||||||
export const generateImageThumbnailUsingCanvas = async (blob: Blob) => {
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
const canvasCtx = ensure(canvas.getContext("2d"));
|
|
||||||
|
|
||||||
const imageURL = URL.createObjectURL(blob);
|
|
||||||
await withTimeout(
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
const image = new Image();
|
|
||||||
image.setAttribute("src", imageURL);
|
|
||||||
image.onload = () => {
|
|
||||||
try {
|
|
||||||
URL.revokeObjectURL(imageURL);
|
|
||||||
const { width, height } = scaledThumbnailDimensions(
|
|
||||||
image.width,
|
|
||||||
image.height,
|
|
||||||
maxThumbnailDimension,
|
|
||||||
);
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
canvasCtx.drawImage(image, 0, 0, width, height);
|
|
||||||
resolve(undefined);
|
|
||||||
} catch (e: unknown) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
|
||||||
reject(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
30 * 1000,
|
|
||||||
);
|
|
||||||
|
|
||||||
return await compressedJPEGData(canvas);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const generateVideoThumbnailUsingCanvas = async (blob: Blob) => {
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
const canvasCtx = ensure(canvas.getContext("2d"));
|
|
||||||
|
|
||||||
const videoURL = URL.createObjectURL(blob);
|
|
||||||
await withTimeout(
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
const video = document.createElement("video");
|
|
||||||
video.preload = "metadata";
|
|
||||||
video.src = videoURL;
|
|
||||||
video.addEventListener("loadeddata", () => {
|
|
||||||
try {
|
|
||||||
URL.revokeObjectURL(videoURL);
|
|
||||||
const { width, height } = scaledThumbnailDimensions(
|
|
||||||
video.videoWidth,
|
|
||||||
video.videoHeight,
|
|
||||||
maxThumbnailDimension,
|
|
||||||
);
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
canvasCtx.drawImage(video, 0, 0, width, height);
|
|
||||||
resolve(undefined);
|
|
||||||
} catch (e) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
|
||||||
reject(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
30 * 1000,
|
|
||||||
);
|
|
||||||
|
|
||||||
return await compressedJPEGData(canvas);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the size of the thumbnail to create for an image with the given
|
* Compute optimal dimensions for a resized version of an image while
|
||||||
* {@link width} and {@link height}.
|
* maintaining aspect ratio of the source image.
|
||||||
*
|
*
|
||||||
* This function calculates a new size of an image for limiting it to maximum
|
* @param width The width of the source image.
|
||||||
* width and height (both specified by {@link maxDimension}), while maintaining
|
*
|
||||||
* aspect ratio.
|
* @param height The height of the source image.
|
||||||
|
*
|
||||||
|
* @param maxDimension The maximum width of height of the resized image.
|
||||||
|
*
|
||||||
|
* This function returns a new size limiting it to maximum width and height
|
||||||
|
* (both specified by {@link maxDimension}), while maintaining aspect ratio of
|
||||||
|
* the source {@link width} and {@link height}.
|
||||||
*
|
*
|
||||||
* It returns `{0, 0}` for invalid inputs.
|
* It returns `{0, 0}` for invalid inputs.
|
||||||
*/
|
*/
|
||||||
const scaledThumbnailDimensions = (
|
export const scaledImageDimensions = (
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
maxDimension: number,
|
maxDimension: number,
|
||||||
): { width: number; height: number } => {
|
): { width: number; height: number } => {
|
||||||
if (width === 0 || height === 0) return { width: 0, height: 0 };
|
if (width == 0 || height == 0) return { width: 0, height: 0 };
|
||||||
const widthScaleFactor = maxDimension / width;
|
const widthScaleFactor = maxDimension / width;
|
||||||
const heightScaleFactor = maxDimension / height;
|
const heightScaleFactor = maxDimension / height;
|
||||||
const scaleFactor = Math.min(widthScaleFactor, heightScaleFactor);
|
const scaleFactor = Math.min(widthScaleFactor, heightScaleFactor);
|
||||||
const thumbnailDimensions = {
|
const resizedDimensions = {
|
||||||
width: Math.round(width * scaleFactor),
|
width: Math.round(width * scaleFactor),
|
||||||
height: Math.round(height * scaleFactor),
|
height: Math.round(height * scaleFactor),
|
||||||
};
|
};
|
||||||
if (thumbnailDimensions.width === 0 || thumbnailDimensions.height === 0)
|
if (resizedDimensions.width == 0 || resizedDimensions.height == 0)
|
||||||
return { width: 0, height: 0 };
|
return { width: 0, height: 0 };
|
||||||
return thumbnailDimensions;
|
return resizedDimensions;
|
||||||
};
|
};
|
||||||
|
|
||||||
const compressedJPEGData = async (canvas: HTMLCanvasElement) => {
|
|
||||||
let blob: Blob | undefined | null;
|
|
||||||
let prevSize = Number.MAX_SAFE_INTEGER;
|
|
||||||
let quality = 0.7;
|
|
||||||
|
|
||||||
do {
|
|
||||||
if (blob) prevSize = blob.size;
|
|
||||||
blob = await new Promise((resolve) => {
|
|
||||||
canvas.toBlob((blob) => resolve(blob), "image/jpeg", quality);
|
|
||||||
});
|
|
||||||
quality -= 0.1;
|
|
||||||
} while (
|
|
||||||
quality >= 0.5 &&
|
|
||||||
blob &&
|
|
||||||
blob.size > maxThumbnailSize &&
|
|
||||||
percentageSizeDiff(blob.size, prevSize) >= 10
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Uint8Array(await ensure(blob).arrayBuffer());
|
|
||||||
};
|
|
||||||
|
|
||||||
const percentageSizeDiff = (
|
|
||||||
newThumbnailSize: number,
|
|
||||||
oldThumbnailSize: number,
|
|
||||||
) => ((oldThumbnailSize - newThumbnailSize) * 100) / oldThumbnailSize;
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user