2021-08-13 04:18:11 +00:00
|
|
|
import { FILE_TYPE } from 'services/fileService';
|
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,
|
2021-08-13 03:38:02 +00:00
|
|
|
fileType: FILE_TYPE
|
|
|
|
): Promise<{ thumbnail: Uint8Array; hasStaticThumbnail: boolean }> {
|
2021-08-09 09:07:47 +00:00
|
|
|
try {
|
|
|
|
let hasStaticThumbnail = false;
|
|
|
|
let canvas = null;
|
|
|
|
try {
|
2021-08-13 03:38:02 +00:00
|
|
|
if (fileType === FILE_TYPE.IMAGE) {
|
|
|
|
canvas = await generateImageThumbnail(file);
|
2021-08-09 09:07:47 +00:00
|
|
|
} else {
|
2021-08-13 03:38:02 +00:00
|
|
|
canvas = await generateVideoThumbnail(file);
|
2021-08-09 09:07:47 +00:00
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
logError(e);
|
|
|
|
// ignore and set staticThumbnail
|
|
|
|
hasStaticThumbnail = true;
|
|
|
|
}
|
2021-08-13 03:38:02 +00:00
|
|
|
const thumbnailBlob = await thumbnailCanvasToBlob(canvas);
|
|
|
|
const thumbnail = await getUint8ArrayView(reader, thumbnailBlob);
|
2021-08-09 09:07:47 +00:00
|
|
|
return { thumbnail, hasStaticThumbnail };
|
|
|
|
} catch (e) {
|
|
|
|
logError(e, 'Error generating thumbnail');
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-13 03:38:02 +00:00
|
|
|
export async function generateImageThumbnail(file: globalThis.File) {
|
2021-08-09 09:07:47 +00:00
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
const canvasCTX = canvas.getContext('2d');
|
|
|
|
|
2021-08-13 03:38:02 +00:00
|
|
|
let imageURL = null;
|
2021-08-09 09:07:47 +00:00
|
|
|
let timeout = null;
|
|
|
|
|
|
|
|
if (fileIsHEIC(file.name)) {
|
2021-08-13 03:38:02 +00:00
|
|
|
file = new globalThis.File([await convertHEIC2JPEG(file)], null, null);
|
2021-08-09 09:07:47 +00:00
|
|
|
}
|
|
|
|
let image = new Image();
|
|
|
|
imageURL = URL.createObjectURL(file);
|
|
|
|
image.setAttribute('src', imageURL);
|
|
|
|
await new Promise((resolve, reject) => {
|
|
|
|
image.onload = () => {
|
|
|
|
try {
|
|
|
|
const thumbnailWidth =
|
2021-08-13 03:38:02 +00:00
|
|
|
(image.width * THUMBNAIL_HEIGHT) / image.height;
|
2021-08-09 09:07:47 +00:00
|
|
|
canvas.width = thumbnailWidth;
|
|
|
|
canvas.height = THUMBNAIL_HEIGHT;
|
|
|
|
canvasCTX.drawImage(
|
|
|
|
image,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
thumbnailWidth,
|
2021-08-13 03:38:02 +00:00
|
|
|
THUMBNAIL_HEIGHT
|
2021-08-09 09:07:47 +00:00
|
|
|
);
|
|
|
|
image = null;
|
|
|
|
clearTimeout(timeout);
|
|
|
|
resolve(null);
|
|
|
|
} catch (e) {
|
|
|
|
reject(e);
|
|
|
|
logError(e);
|
2021-08-13 03:38:02 +00:00
|
|
|
reject(
|
|
|
|
Error(
|
|
|
|
`${CustomError.THUMBNAIL_GENERATION_FAILED} err: ${e}`
|
|
|
|
)
|
|
|
|
);
|
2021-08-09 09:07:47 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
timeout = setTimeout(
|
|
|
|
() =>
|
|
|
|
reject(
|
2021-08-13 03:38:02 +00:00
|
|
|
Error(
|
|
|
|
`wait time exceeded for format ${
|
|
|
|
file.name.split('.').slice(-1)[0]
|
|
|
|
}`
|
|
|
|
)
|
2021-08-09 09:07:47 +00:00
|
|
|
),
|
2021-08-13 03:38:02 +00:00
|
|
|
WAIT_TIME_THUMBNAIL_GENERATION
|
2021-08-09 09:07:47 +00:00
|
|
|
);
|
|
|
|
});
|
|
|
|
return canvas;
|
|
|
|
}
|
|
|
|
|
2021-08-13 03:38:02 +00:00
|
|
|
export async function generateVideoThumbnail(file: globalThis.File) {
|
2021-08-09 09:07:47 +00:00
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
const canvasCTX = canvas.getContext('2d');
|
|
|
|
|
2021-08-13 03:38:02 +00:00
|
|
|
let videoURL = null;
|
|
|
|
let timeout = null;
|
2021-08-09 09:07:47 +00:00
|
|
|
|
|
|
|
await new Promise((resolve, reject) => {
|
|
|
|
let video = document.createElement('video');
|
|
|
|
videoURL = URL.createObjectURL(file);
|
|
|
|
video.addEventListener('timeupdate', function () {
|
|
|
|
try {
|
|
|
|
if (!video) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const thumbnailWidth =
|
2021-08-13 03:38:02 +00:00
|
|
|
(video.videoWidth * THUMBNAIL_HEIGHT) / video.videoHeight;
|
2021-08-09 09:07:47 +00:00
|
|
|
canvas.width = thumbnailWidth;
|
|
|
|
canvas.height = THUMBNAIL_HEIGHT;
|
|
|
|
canvasCTX.drawImage(
|
|
|
|
video,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
thumbnailWidth,
|
2021-08-13 03:38:02 +00:00
|
|
|
THUMBNAIL_HEIGHT
|
2021-08-09 09:07:47 +00:00
|
|
|
);
|
|
|
|
video = null;
|
|
|
|
clearTimeout(timeout);
|
|
|
|
resolve(null);
|
|
|
|
} catch (e) {
|
2021-08-13 03:38:02 +00:00
|
|
|
const err = Error(
|
|
|
|
`${CustomError.THUMBNAIL_GENERATION_FAILED} err: ${e}`
|
|
|
|
);
|
2021-08-09 11:25:06 +00:00
|
|
|
logError(err);
|
|
|
|
reject(err);
|
2021-08-09 09:07:47 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
video.preload = 'metadata';
|
|
|
|
video.src = videoURL;
|
|
|
|
video.currentTime = 3;
|
2021-08-13 03:38:02 +00:00
|
|
|
timeout = setTimeout(
|
2021-08-09 09:07:47 +00:00
|
|
|
() =>
|
2021-08-13 03:38:02 +00:00
|
|
|
reject(
|
|
|
|
Error(
|
|
|
|
`wait time exceeded for format ${
|
|
|
|
file.name.split('.').slice(-1)[0]
|
|
|
|
}`
|
|
|
|
)
|
|
|
|
),
|
|
|
|
WAIT_TIME_THUMBNAIL_GENERATION
|
2021-08-09 09:07:47 +00:00
|
|
|
);
|
|
|
|
});
|
2021-08-09 11:25:06 +00:00
|
|
|
return canvas;
|
2021-08-09 09:07:47 +00:00
|
|
|
}
|
|
|
|
|
2021-08-13 03:38:02 +00:00
|
|
|
export async function thumbnailCanvasToBlob(canvas: HTMLCanvasElement) {
|
2021-08-09 09:07:47 +00:00
|
|
|
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',
|
2021-08-13 03:38:02 +00:00
|
|
|
quality
|
2021-08-09 09:07:47 +00:00
|
|
|
);
|
|
|
|
});
|
|
|
|
thumbnailBlob = thumbnailBlob ?? new Blob([]);
|
|
|
|
} while (
|
|
|
|
thumbnailBlob.size > MIN_THUMBNAIL_SIZE &&
|
2021-08-13 03:38:02 +00:00
|
|
|
attempts <= MAX_ATTEMPTS
|
2021-08-09 09:07:47 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
return thumbnailBlob;
|
|
|
|
}
|