You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, durationSeconds = "5", titleText = "CINEMATIC TRAILER", subtitleText = "A MASTERPIECE BY AI") {
const duration = parseFloat(durationSeconds) || 5;
const width = 800;
const height = 450; // 16:9 cinematic aspect ratio
// Create the container
const container = document.createElement('div');
container.style.position = 'relative';
container.style.width = '100%';
container.style.maxWidth = width + 'px';
container.style.aspectRatio = '16 / 9';
container.style.backgroundColor = '#050505';
container.style.color = '#fff';
container.style.display = 'flex';
container.style.alignItems = 'center';
container.style.justifyContent = 'center';
container.style.overflow = 'hidden';
container.style.borderRadius = '8px';
container.style.boxShadow = '0 4px 15px rgba(0,0,0,0.5)';
container.style.fontFamily = "'Montserrat', sans-serif";
// Status message while generating
const statusMsg = document.createElement('div');
statusMsg.innerText = "🎬 Generating AI Trailer... Please wait.";
statusMsg.style.fontSize = '1.2rem';
statusMsg.style.textAlign = 'center';
statusMsg.style.zIndex = '1';
container.appendChild(statusMsg);
// Inject Cinematic Font
const fontUrl = 'https://fonts.googleapis.com/css2?family=Montserrat:wght@900&display=swap';
if (!document.querySelector(`link[href="${fontUrl}"]`)) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = fontUrl;
document.head.appendChild(link);
}
// Wait for the font to be ready, then begin processing
document.fonts.ready.then(() => {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// Setup MediaRecorder
if (typeof MediaRecorder === 'undefined') {
statusMsg.innerText = '⚠️ MediaRecorder is not supported in this browser. Try Chrome, Firefox, or Safari 14+';
return;
}
const stream = canvas.captureStream(30); // Capture at 30 fps
let options = { mimeType: 'video/webm; codecs=vp9' };
if (!MediaRecorder.isTypeSupported(options.mimeType)) {
options = { mimeType: 'video/webm' };
if (!MediaRecorder.isTypeSupported(options.mimeType)) {
options = { mimeType: 'video/mp4' };
if (!MediaRecorder.isTypeSupported(options.mimeType)) {
options = {}; // Let browser choose the format
}
}
}
const recorder = new MediaRecorder(stream, options);
const chunks = [];
recorder.ondataavailable = e => {
if (e.data && e.data.size > 0) chunks.push(e.data);
};
recorder.onstop = () => {
const blob = new Blob(chunks, { type: chunks[0] ? chunks[0].type : 'video/mp4' });
const videoUrl = URL.createObjectURL(blob);
// Replace status with generated video element
const video = document.createElement('video');
video.src = videoUrl;
video.controls = true;
video.autoplay = true;
video.loop = true;
video.style.width = '100%';
video.style.height = '100%';
video.style.objectFit = 'cover';
video.style.backgroundColor = '#000';
container.innerHTML = ''; // Clear container
container.appendChild(video);
// Add an overlay Download button
const dlBtn = document.createElement('a');
dlBtn.href = videoUrl;
// Determine video extension based on actual mimetype generated
const ext = blob.type.includes('mp4') ? 'mp4' : 'webm';
dlBtn.download = `ai_trailer.${ext}`;
dlBtn.innerText = '📥 Download Video';
dlBtn.style.position = 'absolute';
dlBtn.style.bottom = '15px';
dlBtn.style.right = '15px';
dlBtn.style.padding = '10px 15px';
dlBtn.style.backgroundColor = '#ffffff';
dlBtn.style.color = '#000000';
dlBtn.style.textDecoration = 'none';
dlBtn.style.borderRadius = '6px';
dlBtn.style.fontWeight = 'bold';
dlBtn.style.fontSize = '14px';
dlBtn.style.zIndex = '10';
dlBtn.style.boxShadow = '0 2px 10px rgba(0,0,0,0.5)';
dlBtn.style.transition = 'background-color 0.2s';
dlBtn.onmouseenter = () => dlBtn.style.backgroundColor = '#e0e0e0';
dlBtn.onmouseleave = () => dlBtn.style.backgroundColor = '#ffffff';
container.appendChild(dlBtn);
};
// Start recording with 250ms timeslices
recorder.start(250);
let startTime = null;
// Render animation frames
function drawFrame(now) {
if (!startTime) startTime = now;
const elapsed = (now - startTime) / 1000;
const progress = elapsed / duration;
if (elapsed > duration) {
recorder.stop();
return; // Stop animation loop
}
// 1. Draw Background
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, width, height);
// 2. Compute Ken Burns effect (zoom out and slight pan)
const imgAspect = originalImg.width / originalImg.height;
const canvasAspect = width / height;
let drawW, drawH;
if (imgAspect > canvasAspect) {
drawH = height;
drawW = height * imgAspect;
} else {
drawW = width;
drawH = width / imgAspect;
}
const scale = 1.3 - (0.15 * progress); // Zoom out smoothly
const curW = drawW * scale;
const curH = drawH * scale;
const panX = (width - curW) / 2 + (80 * progress); // Pan slightly right over time
const panY = (height - curH) / 2;
// 3. Render and Apply Cinematic Grading
ctx.save();
ctx.globalCompositeOperation = 'source-over';
ctx.drawImage(originalImg, panX, panY, curW, curH);
// Cinematic "Teal & Orange" tint
ctx.globalCompositeOperation = 'multiply';
ctx.fillStyle = '#14212e'; // Dark moody teal
ctx.fillRect(0, 0, width, height);
ctx.globalCompositeOperation = 'overlay';
// Pulsing warm highlights
const pulse = Math.sin(progress * Math.PI) * 0.15;
ctx.fillStyle = `rgba(255, 120, 40, ${0.1 + pulse})`;
ctx.fillRect(0, 0, width, height);
ctx.restore();
// 4. Heavy Vignette for drama
const gradient = ctx.createRadialGradient(width/2, height/2, height/4, width/2, height/2, width*0.8);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, 'rgba(0,0,0,0.95)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
// 5. Simulate floating cinematic ash/sparks
ctx.fillStyle = 'rgba(255, 180, 100, 0.4)';
for (let i = 0; i < 45; i++) {
const px = (Math.sin(i * 12.34 + elapsed * 0.8) * 0.5 + 0.5) * width;
const py = ((i * 43.21 - elapsed * 60) % height + height) % height; // Float upwards
const radius = (i % 3) + 1;
ctx.beginPath();
ctx.arc(px, py, radius, 0, Math.PI * 2);
ctx.fill();
}
// 6. Letterbox (Black Cinema Bars)
const barHeight = height * 0.11;
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, width, barHeight);
ctx.fillRect(0, height - barHeight, width, barHeight);
// 7. Dramatic Text Overlay Sequence
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = `900 ${height * 0.11}px 'Montserrat', sans-serif`;
ctx.shadowColor = '#000';
ctx.shadowBlur = 20;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
let txt = "";
let alpha = 0;
const isGlitching = Math.random() > 0.94; // Random frame glitch
if (progress < 0.33) {
txt = "IN A WORLD...";
alpha = Math.sin((progress / 0.33) * Math.PI); // Fade in/out
} else if (progress < 0.66) {
txt = subtitleText.toUpperCase();
alpha = Math.sin(((progress - 0.33) / 0.33) * Math.PI);
} else {
txt = titleText.toUpperCase();
alpha = Math.min((progress - 0.66) / 0.1, 1); // Fade in and stay
}
// Draw Text with Glitch RGB Split effect
if (alpha > 0) {
let rx = width / 2;
let ry = height / 2;
// Position jitter during glitch
if (isGlitching && alpha > 0.4) {
rx += (Math.random() - 0.5) * 15;
ry += (Math.random() - 0.5) * 5;
}
// RGB channels
if (isGlitching && alpha > 0.4) {
ctx.fillStyle = `rgba(255, 0, 80, ${alpha})`;
ctx.fillText(txt, rx - 8, ry);
ctx.fillStyle = `rgba(0, 255, 255, ${alpha})`;
ctx.fillText(txt, rx + 8, ry);
}
// Base white text
ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`;
ctx.fillText(txt, rx, ry);
}
// 8. Overall Master Fade In/Out
if (progress < 0.05) {
ctx.fillStyle = `rgba(0,0,0,${1 - progress/0.05})`;
ctx.fillRect(0,0,width,height);
} else if (progress > 0.95) {
ctx.fillStyle = `rgba(0,0,0,${(progress-0.95)/0.05})`;
ctx.fillRect(0,0,width,height);
}
requestAnimationFrame(drawFrame);
}
// Start drawing sequence
requestAnimationFrame(drawFrame);
}).catch(err => {
statusMsg.innerText = "Error initializing fonts or rendering: " + err.message;
});
return container;
}
Apply Changes