Please bookmark this page to avoid losing your image tool!

Image To Movie 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.
async function processImage(originalImg, durationSecs = "3", cinematicBars = "true", zoomEffect = "zoom_in", cinematicFilter = "true") {
    // Parse parameters
    const durationMs = (parseFloat(durationSecs) || 3) * 1000;
    const addBars = cinematicBars === "true";
    const useFilter = cinematicFilter === "true";
    
    // Create container
    const container = document.createElement('div');
    container.style.cssText = "display: flex; flex-direction: column; align-items: center; font-family: sans-serif; width: 100%; max-width: 800px; margin: auto;";
    
    // Status text / Preview Box
    const statusBox = document.createElement('div');
    statusBox.style.cssText = "padding: 15px; background: #222; border-radius: 8px; margin-bottom: 15px; width: 100%; text-align: center; color: #fff; font-weight: bold; letter-spacing: 1px;";
    statusBox.innerText = `Generating Movie... (duration: ${durationSecs}s)`;
    container.appendChild(statusBox);

    // Coordinate Canvas size (Limit to Standard 1080p max resolution for smooth playback)
    const MAX_WIDTH = 1920;
    const MAX_HEIGHT = 1080;
    let width = originalImg.width;
    let height = originalImg.height;

    if (width > MAX_WIDTH || height > MAX_HEIGHT) {
        const ratio = Math.min(MAX_WIDTH / width, MAX_HEIGHT / height);
        width = Math.round(width * ratio);
        height = Math.round(height * ratio);
    }

    // Main rendering canvas (kept in memory, off-screen to generate video)
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    const ctx = canvas.getContext('2d', { alpha: false });
    
    // Preview canvas (added to UI so users don't stare at a blank screen)
    const previewCanvas = document.createElement('canvas');
    const PREVIEW_MAX = 600;
    let pWidth = width, pHeight = height;
    if(pWidth > PREVIEW_MAX || pHeight > PREVIEW_MAX) {
        const pRatio = Math.min(PREVIEW_MAX / pWidth, PREVIEW_MAX / pHeight);
        pWidth = Math.round(pWidth * pRatio);
        pHeight = Math.round(pHeight * pRatio);
    }
    previewCanvas.width = pWidth;
    previewCanvas.height = pHeight;
    previewCanvas.style.cssText = "border: 1px solid #333; border-radius: 6px; box-shadow: 0 4px 15px rgba(0,0,0,0.3); max-width: 100%; background: #000;";
    const pCtx = previewCanvas.getContext('2d');
    container.appendChild(previewCanvas);

    // Capability Checks
    if (!canvas.captureStream || typeof MediaRecorder === 'undefined') {
        statusBox.innerText = "Compatibility Error: Your browser doesn't support canvas video recording.";
        statusBox.style.backgroundColor = "#ffdddd";
        statusBox.style.color = "#a00";
        return container;
    }

    let stream;
    try {
        stream = canvas.captureStream(30); // Capture stream at 30 FPS
    } catch(err) {
        statusBox.innerText = "Security Error: CORS restrictions strictly block capturing video from this image.";
        statusBox.style.backgroundColor = "#ffdddd";
        statusBox.style.color = "#a00";
        return container;
    }

    // Check supported formats
    const mimeTypes = ['video/webm;codecs=vp9', 'video/webm;codecs=vp8', 'video/webm', 'video/mp4'];
    let mimeType = '';
    if (typeof MediaRecorder.isTypeSupported === 'function') {
        for (let mt of mimeTypes) {
            if (MediaRecorder.isTypeSupported(mt)) {
                mimeType = mt;
                break;
            }
        }
    }
    
    // Initialize Media Recorder
    let recorder;
    try {
        const options = mimeType ? { mimeType, videoBitsPerSecond: 2500000 } : {};
        recorder = new MediaRecorder(stream, options);
    } catch(e) {
        try {
            recorder = new MediaRecorder(stream);
        } catch(e2) {
            statusBox.innerText = "Error initializing MediaRecorder: " + e2.message;
            return container;
        }
    }

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

    recorder.onstop = () => {
        const blob = new Blob(chunks, { type: recorder.mimeType || mimeType || 'video/webm' });
        const videoUrl = URL.createObjectURL(blob);
        
        // Setup final Video Elements
        const video = document.createElement('video');
        video.src = videoUrl;
        video.controls = true;
        video.autoplay = true;
        video.loop = true;
        video.playsInline = true;
        video.style.cssText = "max-width: 100%; border-radius: 8px; box-shadow: 0 6px 15px rgba(0,0,0,0.4); background: #000;";

        statusBox.innerText = "🎬 Movie Complete! Play or download below.";
        statusBox.style.backgroundColor = "#28a745";
        statusBox.style.color = "#fff";

        // Replace preview with actual video output
        container.replaceChild(video, previewCanvas);

        const downloadLink = document.createElement('a');
        downloadLink.href = videoUrl;
        let ext = blob.type.includes("mp4") ? "mp4" : "webm";
        downloadLink.download = `cinematic-movie.${ext}`;
        downloadLink.innerText = "⬇ Download Movie";
        downloadLink.style.cssText = `
            display: inline-block; 
            margin-top: 20px; 
            padding: 12px 24px; 
            background-color: #007BFF; 
            color: white; 
            text-decoration: none; 
            border-radius: 6px; 
            font-weight: bold; 
            cursor: pointer; 
            border: none; 
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            transition: background 0.2s;
        `;
        downloadLink.onmouseover = () => downloadLink.style.backgroundColor = "#0056b3";
        downloadLink.onmouseout  = () => downloadLink.style.backgroundColor = "#007BFF";
        
        container.appendChild(downloadLink);
    };

    let start = null;

    // Animation Render Loop (Ken Burns, Filters, Cinematics)
    function renderFrame(now) {
        if (!start) start = now;
        const elapsed = now - start;
        let progress = elapsed / durationMs;
        if (progress > 1) progress = 1;

        // Reset base bounds
        let scale = 1.0;
        const maxScale = 1.15;
        const paddingX = (width * maxScale) - width; 
        
        if (zoomEffect === 'zoom_in') {
            scale = 1.0 + (0.15 * progress);
        } else if (zoomEffect === 'zoom_out') {
            scale = maxScale - (0.15 * progress);
        } else if (zoomEffect === 'pan_left' || zoomEffect === 'pan_right') {
            scale = maxScale;
        }

        // Compute Dimensions
        const vWidth = width * scale;
        const vHeight = height * scale;
        let drawX = (width - vWidth) / 2;
        let drawY = (height - vHeight) / 2;

        if (zoomEffect === 'pan_left') {
            drawX = -paddingX + (paddingX * progress);
        } else if (zoomEffect === 'pan_right') {
            drawX = 0 - (paddingX * progress); 
        }

        // 1. Clear background
        ctx.fillStyle = '#000';
        ctx.fillRect(0, 0, width, height);

        // 2. Draw scaled & transformed main picture
        if (useFilter && ctx.filter !== undefined) {
            ctx.filter = 'contrast(1.1) brightness(0.95) saturate(1.15)';
        }
        ctx.drawImage(originalImg, drawX, drawY, vWidth, vHeight);
        if (useFilter && ctx.filter !== undefined) {
            ctx.filter = 'none';
        }

        // 3. Cinematic Vignette (Edge shadows)
        if (useFilter) {
            const cx = width / 2;
            const cy = height / 2;
            const rad = Math.max(width, height) * 0.75;
            const gradient = ctx.createRadialGradient(cx, cy, rad * 0.5, cx, cy, rad);
            gradient.addColorStop(0, 'rgba(0,0,0,0)');
            gradient.addColorStop(1, 'rgba(0,0,0,0.85)');
            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, width, height);
        }

        // 4. Cinematic Letterboxing (Top and Bottom Bars)
        if (addBars) {
            const barH = height * 0.12; // 12% crop lines
            ctx.fillStyle = '#000';
            ctx.fillRect(0, 0, width, barH);
            ctx.fillRect(0, height - barH, width, barH);
        }

        // 5. Blit to smaller UI preview
        pCtx.drawImage(canvas, 0, 0, pWidth, pHeight);

        // Update progress readout occasionally
        if (Math.round(progress * 100) % 5 === 0 && progress < 1) {
            statusBox.innerText = `Generating Movie... ${Math.round(progress * 100)}%`;
        }

        // Loop Management
        if (progress < 1) {
            requestAnimationFrame(renderFrame);
        } else {
            recorder.stop();
        }
    }

    // Begin Engine
    recorder.start();
    requestAnimationFrame(renderFrame);

    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 cinematic video clips by applying dynamic motion effects and visual enhancements. It can transform a single photo into a short movie featuring effects like zooming (in or out) and panning, combined with professional touches such as cinematic letterboxing (black bars), color filters, and vignette shading. This is ideal for creating engaging social media content, adding life to photo slideshows, or producing atmospheric visual backgrounds for presentations.

Leave a Reply

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