2021-08-09 09:07:47 +00:00
|
|
|
import { FILE_TYPE } from 'pages/gallery';
|
2021-08-09 09:26:49 +00:00
|
|
|
import { CustomError } from 'utils/common/errorUtil';
|
2021-08-09 09:07:47 +00:00
|
|
|
import { fileIsHEIC, convertHEIC2JPEG } from 'utils/file';
|
|
|
|
import { logError } from 'utils/sentry';
|
|
|
|
import { getUint8ArrayView } from './readFileService';
|
|
|
|
|
|
|
|
export const TYPE_IMAGE = 'image';
|
|
|
|
const THUMBNAIL_HEIGHT = 720;
|
|
|
|
const MAX_ATTEMPTS = 3;
|
|
|
|
const MIN_THUMBNAIL_SIZE = 50000;
|
|
|
|
|
|
|
|
|
|
|
|
const WAIT_TIME_THUMBNAIL_GENERATION = 10 * 1000;
|
|
|
|
|
|
|
|
|
|
|
|
export async function generateThumbnail(
|
|
|
|
reader: FileReader,
|
|
|
|
file: globalThis.File,
|
|
|
|
fileType:FILE_TYPE,
|
|
|
|
): Promise<{ thumbnail: Uint8Array, hasStaticThumbnail: boolean }> {
|
|
|
|
try {
|
|
|
|
let hasStaticThumbnail = false;
|
|
|
|
let canvas = null;
|
|
|
|
try {
|
|
|
|
if (fileType===FILE_TYPE.IMAGE) {
|
|
|
|
canvas=await generateImageThumbnail(file);
|
|
|
|
} else {
|
|
|
|
canvas=await generateVideoThumbnail(file);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
logError(e);
|
|
|
|
// ignore and set staticThumbnail
|
|
|
|
hasStaticThumbnail = true;
|
|
|
|
}
|
|
|
|
const thumbnailBlob=await thumbnailCanvasToBlob(canvas);
|
|
|
|
const thumbnail = await getUint8ArrayView(
|
|
|
|
reader,
|
|
|
|
thumbnailBlob,
|
|
|
|
);
|
|
|
|
return { thumbnail, hasStaticThumbnail };
|
|
|
|
} catch (e) {
|
|
|
|
logError(e, 'Error generating thumbnail');
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function generateImageThumbnail(file:globalThis.File) {
|
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
const canvasCTX = canvas.getContext('2d');
|
|
|
|
|
|
|
|
let imageURL=null;
|
|
|
|
let timeout = null;
|
|
|
|
|
|
|
|
if (fileIsHEIC(file.name)) {
|
|
|
|
file = new globalThis.File(
|
|
|
|
[await convertHEIC2JPEG(file)],
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
let image = new Image();
|
|
|
|
imageURL = URL.createObjectURL(file);
|
|
|
|
image.setAttribute('src', imageURL);
|
|
|
|
await new Promise((resolve, reject) => {
|
|
|
|
image.onload = () => {
|
|
|
|
try {
|
|
|
|
const thumbnailWidth =
|
|
|
|
(image.width * THUMBNAIL_HEIGHT) / image.height;
|
|
|
|
canvas.width = thumbnailWidth;
|
|
|
|
canvas.height = THUMBNAIL_HEIGHT;
|
|
|
|
canvasCTX.drawImage(
|
|
|
|
image,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
thumbnailWidth,
|
|
|
|
THUMBNAIL_HEIGHT,
|
|
|
|
);
|
|
|
|
image = null;
|
|
|
|
clearTimeout(timeout);
|
|
|
|
resolve(null);
|
|
|
|
} catch (e) {
|
|
|
|
reject(e);
|
|
|
|
logError(e);
|
2021-08-09 09:26:49 +00:00
|
|
|
reject(Error(`${CustomError.THUMBNAIL_GENERATION_FAILED} err: ${e}`));
|
2021-08-09 09:07:47 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
timeout = setTimeout(
|
|
|
|
() =>
|
|
|
|
reject(
|
|
|
|
Error(`wait time exceeded for format ${file.name.split('.').slice(-1)[0]}`),
|
|
|
|
),
|
|
|
|
WAIT_TIME_THUMBNAIL_GENERATION,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
return canvas;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function generateVideoThumbnail(file:globalThis.File) {
|
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
const canvasCTX = canvas.getContext('2d');
|
|
|
|
|
|
|
|
let videoURL=null;
|
|
|
|
let timeout=null;
|
|
|
|
|
|
|
|
await new Promise((resolve, reject) => {
|
|
|
|
let video = document.createElement('video');
|
|
|
|
videoURL = URL.createObjectURL(file);
|
|
|
|
video.addEventListener('timeupdate', function () {
|
|
|
|
try {
|
|
|
|
if (!video) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const thumbnailWidth =
|
|
|
|
(video.videoWidth * THUMBNAIL_HEIGHT) /
|
|
|
|
video.videoHeight;
|
|
|
|
canvas.width = thumbnailWidth;
|
|
|
|
canvas.height = THUMBNAIL_HEIGHT;
|
|
|
|
canvasCTX.drawImage(
|
|
|
|
video,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
thumbnailWidth,
|
|
|
|
THUMBNAIL_HEIGHT,
|
|
|
|
);
|
|
|
|
video = null;
|
|
|
|
clearTimeout(timeout);
|
|
|
|
resolve(null);
|
|
|
|
} catch (e) {
|
|
|
|
reject(e);
|
|
|
|
logError(e);
|
2021-08-09 09:26:49 +00:00
|
|
|
reject(Error(`${CustomError.THUMBNAIL_GENERATION_FAILED} err: ${e}`));
|
2021-08-09 09:07:47 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
video.preload = 'metadata';
|
|
|
|
video.src = videoURL;
|
|
|
|
video.currentTime = 3;
|
|
|
|
timeout=setTimeout(
|
|
|
|
() =>
|
|
|
|
reject(Error(`wait time exceeded for format ${file.name.split('.').slice(-1)[0]}`)),
|
|
|
|
WAIT_TIME_THUMBNAIL_GENERATION,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function thumbnailCanvasToBlob(canvas:HTMLCanvasElement) {
|
|
|
|
let thumbnailBlob = null;
|
|
|
|
let attempts = 0;
|
|
|
|
let quality = 1;
|
|
|
|
|
|
|
|
do {
|
|
|
|
attempts++;
|
|
|
|
quality /= 2;
|
|
|
|
thumbnailBlob = await new Promise((resolve) => {
|
|
|
|
canvas.toBlob(
|
|
|
|
function (blob) {
|
|
|
|
resolve(blob);
|
|
|
|
},
|
|
|
|
'image/jpeg',
|
|
|
|
quality,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
thumbnailBlob = thumbnailBlob ?? new Blob([]);
|
|
|
|
} while (
|
|
|
|
thumbnailBlob.size > MIN_THUMBNAIL_SIZE &&
|
|
|
|
attempts <= MAX_ATTEMPTS
|
|
|
|
);
|
|
|
|
|
|
|
|
return thumbnailBlob;
|
|
|
|
}
|