You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, youtubeUrlOrVideoId = "dQw4w9WgXcQ", showMockStats = "yes") {
// Extract video ID from typical YouTube URL formats or use directly if it's just an ID
const videoId = (function (url) {
const match = url.match(/(?:youtu\.be\/|youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/);
return match ? match[1] : (url.length === 11 ? url : "dQw4w9WgXcQ");
})(youtubeUrlOrVideoId);
// Default real stats
let title = "Unknown Title";
let author = "Unknown Channel";
let views = "N/A";
let likes = "N/A";
let dislikes = "N/A";
let rating = "N/A";
// Attempt to fetch Title and Channel from public Noembed API
try {
const noembedRes = await fetch(`https://noembed.com/embed?url=https://www.youtube.com/watch?v=${videoId}`);
if (noembedRes.ok) {
const noembedData = await noembedRes.json();
if (noembedData.title) title = noembedData.title;
if (noembedData.author_name) author = noembedData.author_name;
}
} catch (e) {
console.warn("Failed to fetch video details from noembed", e);
}
// Attempt to fetch public Views, Likes, Dislikes via Return YouTube Dislike API
try {
const rydRes = await fetch(`https://returnyoutubedislikeapi.com/votes?videoId=${videoId}`);
if (rydRes.ok) {
const rydData = await rydRes.json();
if (rydData.viewCount !== undefined) views = rydData.viewCount.toLocaleString();
if (rydData.likes !== undefined) likes = rydData.likes.toLocaleString();
if (rydData.dislikes !== undefined) dislikes = rydData.dislikes.toLocaleString();
if (rydData.rating !== undefined) rating = rydData.rating.toFixed(2);
}
} catch (e) {
console.warn("Failed to fetch view/like stats", e);
}
// Create Canvas
const canvas = document.createElement("canvas");
const w = originalImg.width || 1280;
const h = originalImg.height || 720;
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext("2d");
// Draw original image as the background
ctx.drawImage(originalImg, 0, 0, w, h);
// Helpers to generate random mock data for advanced stats
const generateMockCPN = () => {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
return Array.from({ length: 12 }).map(() => chars.charAt(Math.floor(Math.random() * chars.length))).join('');
};
const generateRandomString = (len) => {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
return Array.from({ length: len }).map(() => chars.charAt(Math.floor(Math.random() * chars.length))).join('');
};
// Construct Stats Lines
const lines = [
{ label: "Title", value: title },
{ label: "Channel", value: author },
{ label: "Video ID / sCPN", value: `${videoId} / ${generateMockCPN()}` },
{ label: "Views", value: views },
{ label: "Likes / Dislikes", value: `${likes} / ${dislikes}` }
];
if (showMockStats.toLowerCase() === "yes" || showMockStats === "1" || showMockStats === "true") {
lines.push(
{ label: "Rating", value: `${rating} / 5.00` },
{ label: "Viewport / Frames", value: `${w}x${h} / 0 dropped of ${(Math.random() * 5000 + 1000).toFixed(0)}` },
{ label: "Current / Optimal Res", value: `${w}x${h}@60 / ${w}x${h}@60` },
{ label: "Volume / Normalized", value: "100% / 100% (content loudness -1.2dB)" },
{ label: "Codecs", value: "vp09.00.51.08.01.01.01.01 (248) / opus (251)" },
{ label: "Host", value: `r${Math.floor(Math.random() * 8) + 1}---sn-${generateRandomString(8)}.googlevideo.com` },
{ label: "Buffer Health", value: (Math.random() * 30 + 10).toFixed(1) + " s" },
{ label: "Connection Speed", value: (Math.random() * 50000 + 10000).toFixed(0) + " Kbps" },
{ label: "Network Activity", value: "0 KB" }
);
}
// Determine font size dynamically based on canvas dimensions to fit appropriately
let fontSize = Math.max(10, Math.floor(w / 45));
let lineHeight = fontSize * 1.6;
// Shrink font size if the height of the generated table would exceed limits
const requiredTotalHeight = lineHeight * (lines.length + 4);
if (requiredTotalHeight > h * 0.9) {
fontSize = Math.floor((h * 0.9) / ((lines.length + 4) * 1.6));
if (fontSize < 8) fontSize = 8;
lineHeight = fontSize * 1.6;
}
// Measure widths to build columns
ctx.font = `${fontSize}px sans-serif`;
let maxLabelWidth = 0;
ctx.font = `bold ${fontSize}px sans-serif`;
lines.forEach(l => {
const lw = ctx.measureText(l.label).width;
if (lw > maxLabelWidth) maxLabelWidth = lw;
});
// Truncate long values to prevent breaking out of the container bounds
const maxValChars = Math.max(10, Math.floor(w * 0.85 / (fontSize * 0.6)) - Math.ceil(maxLabelWidth / fontSize) - 5);
lines.forEach(l => {
if (l.value.length > maxValChars) {
l.value = l.value.substring(0, maxValChars - 3) + "...";
}
});
// Measure values side length
let maxValueWidth = 0;
ctx.font = `normal ${fontSize}px sans-serif`;
lines.forEach(l => {
const vw = ctx.measureText(l.value).width;
if (vw > maxValueWidth) maxValueWidth = vw;
});
// Compile layout parameters
const boxInnerPadding = fontSize * 1.5;
const boxWidth = Math.min(maxLabelWidth + maxValueWidth + boxInnerPadding * 2, w * 0.9);
const boxHeight = lines.length * lineHeight + boxInnerPadding * 2.5;
const boxX = Math.max(10, w * 0.05);
const boxY = Math.max(10, h * 0.05);
// Draw Stats Box Base (Semi-transparent black)
ctx.fillStyle = "rgba(0, 0, 0, 0.75)";
ctx.beginPath();
const r = Math.min(8, fontSize); // border radius
ctx.moveTo(boxX + r, boxY);
ctx.lineTo(boxX + boxWidth - r, boxY);
ctx.quadraticCurveTo(boxX + boxWidth, boxY, boxX + boxWidth, boxY + r);
ctx.lineTo(boxX + boxWidth, boxY + boxHeight - r);
ctx.quadraticCurveTo(boxX + boxWidth, boxY + boxHeight, boxX + boxWidth - r, boxY + boxHeight);
ctx.lineTo(boxX + r, boxY + boxHeight);
ctx.quadraticCurveTo(boxX, boxY + boxHeight, boxX, boxY + boxHeight - r);
ctx.lineTo(boxX, boxY + r);
ctx.quadraticCurveTo(boxX, boxY, boxX + r, boxY);
ctx.closePath();
ctx.fill();
// Draw "Stats for nerds" Header
ctx.fillStyle = "white";
ctx.font = `bold ${fontSize * 1.2}px sans-serif`;
ctx.fillText("Stats for nerds", boxX + boxInnerPadding, boxY + boxInnerPadding);
// Draw pseudo-close "✕" icon
ctx.font = `normal ${fontSize * 1.2}px sans-serif`;
ctx.fillText("✕", boxX + boxWidth - boxInnerPadding - fontSize * 0.5, boxY + boxInnerPadding);
// Draw Separator Line under title
ctx.strokeStyle = "rgba(255, 255, 255, 0.25)";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(boxX + boxInnerPadding, boxY + boxInnerPadding + fontSize * 0.8);
ctx.lineTo(boxX + boxWidth - boxInnerPadding, boxY + boxInnerPadding + fontSize * 0.8);
ctx.stroke();
// Draw Data Entries line by line
let currentY = boxY + boxInnerPadding * 2.2;
for (let i = 0; i < lines.length; i++) {
const lineData = lines[i];
// Label Column
ctx.fillStyle = "#bbbbbb"; // Customary YouTube grey for labels
ctx.font = `bold ${fontSize}px sans-serif`;
ctx.fillText(lineData.label, boxX + boxInnerPadding, currentY);
// Value Column
ctx.fillStyle = "white"; // White for actual values
ctx.font = `normal ${fontSize}px sans-serif`;
ctx.fillText(lineData.value, boxX + boxInnerPadding + maxLabelWidth + fontSize, currentY);
currentY += lineHeight;
}
return canvas;
}
Apply Changes