Please bookmark this page to avoid losing your image tool!

Photo To Video Converter

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, videoDurationSeconds = 5, useAnimatedZoom = 1) {
    const duration = parseFloat(videoDurationSeconds) || 5;
    const animatedZoom = parseInt(useAnimatedZoom) === 1;
    
    const container = document.createElement('div');
    container.style.fontFamily = 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif';
    container.style.textAlign = 'center';
    container.style.padding = '30px';
    container.style.maxWidth = '600px';
    container.style.margin = '0 auto';
    container.style.backgroundColor = '#ffffff';
    container.style.borderRadius = '12px';
    container.style.boxShadow = '0 4px 12px rgba(0,0,0,0.1)';
    
    const title = document.createElement('h3');
    title.style.marginTop = '0';
    title.style.color = '#333';
    title.textContent = 'Generating Video...';
    container.appendChild(title);
    
    const loadingStr = document.createElement('p');
    loadingStr.style.color = '#666';
    loadingStr.style.fontSize = '14px';
    loadingStr.textContent = `Processing an exactly ${duration}-second video. Please keep this tab active.`;
    container.appendChild(loadingStr);
    
    const progressBar = document.createElement('div');
    progressBar.style.width = '0%';
    progressBar.style.height = '100%';
    progressBar.style.backgroundColor = '#007bff';
    progressBar.style.borderRadius = '8px';
    progressBar.style.transition = 'width 0.1s linear';
    
    const progressContainer = document.createElement('div');
    progressContainer.style.width = '100%';
    progressContainer.style.height = '16px';
    progressContainer.style.backgroundColor = '#e9ecef';
    progressContainer.style.borderRadius = '8px';
    progressContainer.style.marginTop = '15px';
    progressContainer.style.overflow = 'hidden';
    progressContainer.appendChild(progressBar);
    container.appendChild(progressContainer);
    
    // Create an off-screen canvas specifically for capturing stream
    const canvas = document.createElement('canvas');
    let width = originalImg.width;
    let height = originalImg.height;
    
    // Constrain resolution to 1080p equivalent to manage performance
    const MAX_DIM = 1920;
    if (width > MAX_DIM || height > MAX_DIM) {
        if (width > height) {
            height = Math.round((height * MAX_DIM) / width);
            width = MAX_DIM;
        } else {
            width = Math.round((width * MAX_DIM) / height);
            height = MAX_DIM;
        }
    }
    
    // Width and height should be strictly even number to avoid codec errors
    width = width % 2 === 0 ? width : width - 1;
    height = height % 2 === 0 ? height : height - 1;
    canvas.width = Math.max(2, width);
    canvas.height = Math.max(2, height);
    
    // Defer the recording logic slightly so DOM can mount the loading state
    setTimeout(() => {
        let stream;
        try {
            if (canvas.captureStream) {
                stream = canvas.captureStream(30); // Request 30 FPS
            } else if (canvas.mozCaptureStream) {
                stream = canvas.mozCaptureStream(30);
            } else {
                throw new Error("Video recording via Canvas is not supported in this browser.");
            }
        } catch (err) {
            title.textContent = "Error Initializing";
            loadingStr.textContent = "Error capturing stream: " + err.message;
            return;
        }

        let mimeType = 'video/webm;codecs=vp9';
        if (!window.MediaRecorder || !MediaRecorder.isTypeSupported(mimeType)) {
            mimeType = 'video/webm;codecs=vp8';
            if (!window.MediaRecorder || !MediaRecorder.isTypeSupported(mimeType)) {
                mimeType = 'video/webm';
                if (!window.MediaRecorder || !MediaRecorder.isTypeSupported(mimeType)) {
                    mimeType = 'video/mp4';
                    if (!window.MediaRecorder || !MediaRecorder.isTypeSupported(mimeType)) mimeType = '';
                }
            }
        }

        const options = { videoBitsPerSecond: 5000000 };
        if (mimeType) {
            options.mimeType = mimeType;
        }
        
        let recorder;
        try {
            recorder = new MediaRecorder(stream, options);
        } catch (err) {
            title.textContent = "Error Recording";
            loadingStr.textContent = "Error setting up the video encoder: " + err.message;
            return;
        }

        const chunks = [];
        recorder.ondataavailable = e => {
            if (e.data && e.data.size > 0) chunks.push(e.data);
        };

        recorder.onstop = () => {
            const blobType = mimeType || 'video/webm';
            const blob = new Blob(chunks, { type: blobType });
            const url = URL.createObjectURL(blob);
            
            container.innerHTML = '';
            
            const successTitle = document.createElement('h3');
            successTitle.style.marginTop = '0';
            successTitle.style.color = '#28a745';
            successTitle.textContent = 'Conversion Complete!';
            container.appendChild(successTitle);
            
            const videoElement = document.createElement('video');
            videoElement.src = url;
            videoElement.controls = true;
            videoElement.autoplay = true;
            videoElement.loop = true;
            videoElement.style.maxWidth = '100%';
            videoElement.style.maxHeight = '60vh';
            videoElement.style.border = '2px solid #eaeaea';
            videoElement.style.borderRadius = '8px';
            videoElement.style.backgroundColor = '#000';
            container.appendChild(videoElement);
            
            const gap = document.createElement('div');
            gap.style.height = '20px';
            container.appendChild(gap);
            
            const dwnBtn = document.createElement('a');
            dwnBtn.href = url;
            let ext = 'webm';
            if (blobType.includes('mp4')) ext = 'mp4';
            dwnBtn.download = `photo-to-video.${ext}`;
            dwnBtn.textContent = 'Download Video';
            dwnBtn.style.display = 'inline-block';
            dwnBtn.style.padding = '12px 24px';
            dwnBtn.style.backgroundColor = '#007bff';
            dwnBtn.style.color = '#fff';
            dwnBtn.style.textDecoration = 'none';
            dwnBtn.style.borderRadius = '6px';
            dwnBtn.style.fontWeight = 'bold';
            dwnBtn.style.transition = 'background-color 0.2s';
            dwnBtn.onmouseenter = () => dwnBtn.style.backgroundColor = '#0056b3';
            dwnBtn.onmouseleave = () => dwnBtn.style.backgroundColor = '#007bff';
            container.appendChild(dwnBtn);
        };

        recorder.start();
        const ctx = canvas.getContext('2d');
        ctx.imageSmoothingEnabled = true;
        ctx.imageSmoothingQuality = 'high';

        const startRealTime = Date.now();
        const durationMs = duration * 1000;
        
        const intervalId = setInterval(() => {
            const elapsed = Date.now() - startRealTime;
            let progress = elapsed / durationMs;
            if (progress > 1) progress = 1;
            
            progressBar.style.width = (progress * 100) + '%';
            
            ctx.fillStyle = '#000';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            
            if (animatedZoom) {
                // Subtle zoom inward mapping from 1x to 1.1x scaling (Ken Burns)
                const scale = 1.0 + (progress * 0.1); 
                const cx = canvas.width / 2;
                const cy = canvas.height / 2;
                
                ctx.save();
                ctx.translate(cx, cy);
                ctx.scale(scale, scale);
                ctx.drawImage(originalImg, -cx, -cy, canvas.width, canvas.height);
                ctx.restore();
            } else {
                ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
            }
            
            if (progress >= 1) {
                clearInterval(intervalId);
                setTimeout(() => {
                    if (recorder.state === 'recording') {
                        recorder.stop();
                    }
                }, 100);
            }
        }, 1000 / 30);
        
    }, 100);

    return container;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

This tool converts static images into short videos, adding dynamic movement through an animated zoom effect (Ken Burns style). Users can specify the desired video duration, and the tool processes the image to create a high-quality video file such as WebM or MP4. This is ideal for creating engaging social media content, enhancing digital presentations, or adding subtle animation to still photography for video projects.

Leave a Reply

Your email address will not be published. Required fields are marked *