Files
OpenFrame/lib/client/video-thumbnail.ts
T
yusufipk 4bf6e821af feat: enable S3 video uploads and update related configurations
- Added support for self-hosted S3 video uploads with new environment variables: OPENFRAME_ENABLE_S3_VIDEO_UPLOADS and OPENFRAME_MAX_VIDEO_UPLOAD_BYTES.
- Updated .env.example and .env.docker.example to reflect new configuration options.
- Enhanced Content Security Policy to include origins for S3-compatible storage.
- Updated dependencies for AWS SDK to support new features.
- Refactored upload logic to accommodate both Bunny and S3 upload providers.
- Updated documentation to clarify the usage of direct uploads and S3 configurations.
- Closes #11
2026-05-27 17:04:39 +02:00

64 lines
1.7 KiB
TypeScript

const THUMBNAIL_MAX_WIDTH = 640;
const DEFAULT_SEEK_SECONDS = 1;
export async function captureVideoThumbnail(
file: File,
seekSeconds = DEFAULT_SEEK_SECONDS
): Promise<Blob | null> {
return new Promise((resolve) => {
const objectUrl = URL.createObjectURL(file);
const video = document.createElement('video');
video.preload = 'metadata';
video.muted = true;
video.playsInline = true;
let settled = false;
const finish = (blob: Blob | null) => {
if (settled) return;
settled = true;
video.removeAttribute('src');
video.load();
URL.revokeObjectURL(objectUrl);
resolve(blob);
};
video.onloadedmetadata = () => {
const duration = Number.isFinite(video.duration) ? video.duration : 0;
const target =
duration > 0 ? Math.min(Math.max(seekSeconds, 0), Math.max(0, duration - 0.1)) : 0;
video.currentTime = target;
};
video.onseeked = () => {
try {
const width = video.videoWidth;
const height = video.videoHeight;
if (width <= 0 || height <= 0) {
finish(null);
return;
}
const scale = Math.min(1, THUMBNAIL_MAX_WIDTH / width);
const canvas = document.createElement('canvas');
canvas.width = Math.round(width * scale);
canvas.height = Math.round(height * scale);
const context = canvas.getContext('2d');
if (!context) {
finish(null);
return;
}
context.drawImage(video, 0, 0, canvas.width, canvas.height);
canvas.toBlob((blob) => finish(blob), 'image/jpeg', 0.85);
} catch {
finish(null);
}
};
video.onerror = () => finish(null);
video.src = objectUrl;
});
}