You can edit the below JavaScript code to customize the image tool.
Apply Changes
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;
}
Apply Changes