Fraser's picture
Initial backend setup with Gradio
7ac86fa
/**
* Extract the final frame from a video element as a Blob
* This is used to create continuity between video segments
* Similar to the approach in sora-extend, but using browser Canvas API
*/
export async function extractFinalFrame(
videoElement: HTMLVideoElement
): Promise<Blob> {
return new Promise((resolve, reject) => {
try {
// Create a canvas with the video's dimensions
const canvas = document.createElement('canvas');
canvas.width = videoElement.videoWidth;
canvas.height = videoElement.videoHeight;
const ctx = canvas.getContext('2d');
if (!ctx) {
reject(new Error('Could not get canvas context'));
return;
}
// Draw the current frame onto the canvas
ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
// Convert canvas to blob
canvas.toBlob(
(blob) => {
if (blob) {
resolve(blob);
} else {
reject(new Error('Failed to create blob from canvas'));
}
},
'image/jpeg',
0.95 // High quality for better continuity
);
} catch (error) {
reject(error);
}
});
}
/**
* Seek to the last frame of a video and extract it
* Useful for extracting the final frame before the video ends naturally
*/
export async function seekAndExtractFinalFrame(
videoElement: HTMLVideoElement
): Promise<Blob> {
return new Promise((resolve, reject) => {
const handleSeeked = async () => {
try {
const blob = await extractFinalFrame(videoElement);
videoElement.removeEventListener('seeked', handleSeeked);
resolve(blob);
} catch (error) {
videoElement.removeEventListener('seeked', handleSeeked);
reject(error);
}
};
videoElement.addEventListener('seeked', handleSeeked);
// Seek to 50ms before the end to ensure we get a valid frame
const targetTime = Math.max(0, videoElement.duration - 0.05);
videoElement.currentTime = targetTime;
});
}
/**
* Create a preview URL for a blob (useful for debugging)
*/
export function createBlobPreviewUrl(blob: Blob): string {
return URL.createObjectURL(blob);
}
/**
* Revoke a blob URL to free memory
*/
export function revokeBlobUrl(url: string): void {
URL.revokeObjectURL(url);
}
/**
* Download a blob as a file (useful for debugging/testing)
*/
export function downloadBlob(blob: Blob, filename: string): void {
const url = createBlobPreviewUrl(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
revokeBlobUrl(url);
}