You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, youtubeUrlOrId = "https://www.youtube.com/watch?v=dQw4w9WgXcQ", frameType = "maxresdefault") {
return new Promise(async (resolve) => {
// Robust pattern to extract YouTube Video ID from various URL formats
function getVideoId(url) {
if (!url) return null;
url = String(url).trim();
// If the input is already exactly an 11-character video ID
if (url.length === 11 && /^[a-zA-Z0-9_-]{11}$/.test(url)) return url;
let match = url.match(/(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|shorts\/|watch\?v=|watch\?.+&v=)|m\.youtube\.com\/watch\?v=)([\w-]{11})/);
if (!match) {
match = url.match(/[?&]v=([\w-]{11})/);
}
if (!match) {
match = url.match(/(?:embed|v|shorts)\/([\w-]{11})/);
}
return match ? match[1] : null;
}
const videoId = getVideoId(youtubeUrlOrId) || "dQw4w9WgXcQ";
// Validate the requested frameType
const validTypes = ["maxresdefault", "hqdefault", "mqdefault", "sddefault", "0", "1", "2", "3", "storyboard"];
if (!validTypes.includes(frameType)) {
frameType = "maxresdefault";
}
// Helper to load images with a graceful fallback for strict CORS environments
const loadImage = (src) => {
return new Promise((res) => {
const img = new Image();
img.crossOrigin = "anonymous"; // Try securely first (allows clean Canvas export)
img.onload = () => res({ img, src });
img.onerror = () => {
// Fallback without CORS (will taint canvas, but guarantees display)
const fallbackImg = new Image();
fallbackImg.onload = () => res({ img: fallbackImg, src });
fallbackImg.onerror = () => res(null);
fallbackImg.src = src;
};
img.src = src;
});
};
// If the user requests a 'storyboard', generate an all-in-one grid with main cover + keyframes
if (frameType === "storyboard") {
const coverUrl = `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`;
const f1Url = `https://img.youtube.com/vi/${videoId}/1.jpg`;
const f2Url = `https://img.youtube.com/vi/${videoId}/2.jpg`;
const f3Url = `https://img.youtube.com/vi/${videoId}/3.jpg`;
const [cover, f1, f2, f3] = await Promise.all([
loadImage(coverUrl),
loadImage(f1Url),
loadImage(f2Url),
loadImage(f3Url)
]);
if (!cover || !cover.img) {
const div = document.createElement("div");
div.style.color = "red";
div.innerText = "Error loading storyboard frames. Please check the URL.";
return resolve(div);
}
const cvs = document.createElement("canvas");
// Standard hqdefault is 480x360. Internal keyframes 1, 2, 3 are 120x90.
cvs.width = 480;
cvs.height = 360 + 90 + 20; // 360px (cover) + 10px (gap) + 90px (thumbnails) + 10px (bottom margin)
const ctx = cvs.getContext("2d");
ctx.fillStyle = "#111111"; // Dark background to tie it together nicely
ctx.fillRect(0, 0, cvs.width, cvs.height);
ctx.drawImage(cover.img, 0, 0, 480, 360);
// Align smaller frames evenly across the bottom row
if (f1 && f1.img) ctx.drawImage(f1.img, 30, 370, 120, 90);
if (f2 && f2.img) ctx.drawImage(f2.img, 180, 370, 120, 90);
if (f3 && f3.img) ctx.drawImage(f3.img, 330, 370, 120, 90);
return resolve(cvs);
} else {
// Processing a single frame request
let imgUrl = `https://img.youtube.com/vi/${videoId}/${frameType}.jpg`;
let loaded = await loadImage(imgUrl);
if (!loaded) {
const div = document.createElement("div");
div.style.color = "red";
div.innerText = "Error loading specific video frame.";
return resolve(div);
}
// Fallback for maxresdefault: Many videos do not have a 1080p cover.
// YouTube returns a 120x90 placeholder image gracefully instead of an HTTP 404.
// We check for this specific placeholder size and upgrade it to 'hqdefault' automatically.
if (frameType === "maxresdefault" && loaded.img.width === 120 && loaded.img.height === 90) {
const hqUrl = `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`;
const hqLoaded = await loadImage(hqUrl);
if (hqLoaded && hqLoaded.img) {
loaded = hqLoaded;
}
}
const cvs = document.createElement("canvas");
cvs.width = loaded.img.width;
cvs.height = loaded.img.height;
const ctx = cvs.getContext("2d");
ctx.drawImage(loaded.img, 0, 0);
return resolve(cvs);
}
});
}
Apply Changes