You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, themeColor = "#ef4444") {
// Determine dimensions to maintain proper aspect ratio and scaling
const imgWidth = originalImg.width || 800;
// Create the main wrapper container
const container = document.createElement('div');
container.style.width = imgWidth + 'px';
container.style.maxWidth = '100%';
container.style.boxSizing = 'border-box';
container.style.position = 'relative';
container.style.fontFamily = 'system-ui, -apple-system, sans-serif';
container.style.margin = '0 auto';
// Guard against unrenderable image objects
if (originalImg.width === 0 || originalImg.height === 0) {
container.innerHTML = `<p style="color:red; font-weight:bold;">Error: The provided image has zero width or height.</p>`;
return container;
}
// Set up visualization Canvas
const canvas = document.createElement('canvas');
canvas.width = originalImg.width;
canvas.height = originalImg.height;
canvas.style.display = 'block';
canvas.style.width = '100%';
canvas.style.height = 'auto';
canvas.style.borderRadius = '8px';
canvas.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
container.appendChild(canvas);
const ctx = canvas.getContext('2d');
ctx.drawImage(originalImg, 0, 0);
// Set up Status & Results Panel below the image
const statusPanel = document.createElement('div');
statusPanel.style.marginTop = '20px';
statusPanel.style.background = '#f8f9fa';
statusPanel.style.padding = '25px';
statusPanel.style.borderRadius = '8px';
statusPanel.style.border = '1px solid #e9ecef';
statusPanel.style.transition = 'all 0.3s ease';
statusPanel.innerHTML = `
<div style="text-align:center;">
<h3 style="margin:0 0 10px 0; font-size:18px; font-weight:600; color:#333;">Analyzing Stats for Nerds...</h3>
<p id="tess-status" style="margin:0 0 15px 0; font-family:monospace; font-size:14px; color:#666;">Loading OCR Engine...</p>
<div style="width:100%; max-width:300px; height:6px; background:#ddd; border-radius:3px; overflow:hidden; margin: 0 auto;">
<div id="tess-progress" style="width:0%; height:100%; background:${themeColor}; transition:width 0.2s ease-out;"></div>
</div>
</div>
`;
container.appendChild(statusPanel);
// Asynchronously perform OCR and analysis
(async () => {
try {
// Dynamically load Tesseract.js if not available
if (typeof Tesseract === 'undefined') {
await new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js';
script.onload = resolve;
script.onerror = () => reject(new Error("Failed to load OCR library. Check your network or CORS settings."));
document.head.appendChild(script);
});
}
const statusEl = statusPanel.querySelector('#tess-status');
const progressEl = statusPanel.querySelector('#tess-progress');
// Start Text Recognition
const result = await Tesseract.recognize(originalImg, 'eng', {
logger: m => {
if (statusEl && progressEl) {
if (m.status) {
statusEl.innerText = m.status.charAt(0).toUpperCase() + m.status.slice(1) + '...';
}
if (m.progress) {
progressEl.style.width = Math.round(m.progress * 100) + '%';
}
}
}
});
// Find line containing volume/loudness stats
let targetLine = null;
for (const line of result.data.lines) {
const t = line.text.toLowerCase();
// Common terms or representations found in Youtube's volume stats line
if (t.includes('volume') || t.includes('normal') || t.includes('loudness') || t.includes('%') || t.includes('db')) {
// Make sure it looks like a stats line before confirming
if (t.includes('/') || t.includes('(')) {
targetLine = line;
break;
}
}
}
if (!targetLine) {
statusPanel.innerHTML = `
<div style="text-align:center; color:#b91c1c;">
<h3 style="margin:0 0 10px 0;">No Data Found</h3>
<p style="margin:0;">Could not locate Volume Normalization stats in this image. Please ensure it's a clear screenshot of 'Stats for nerds'.</p>
</div>
`;
return;
}
// --- Visualization (Highlighting the region) ---
const padding = 6;
const bbox = targetLine.bbox;
// Dim the image
ctx.fillStyle = 'rgba(0,0,0,0.65)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Re-draw only the highlighted region
const clearX = Math.max(0, bbox.x0 - padding);
const clearY = Math.max(0, bbox.y0 - padding);
const clearW = (bbox.x1 - bbox.x0) + padding * 2;
const clearH = (bbox.y1 - bbox.y0) + padding * 2;
ctx.clearRect(clearX, clearY, clearW, clearH);
ctx.drawImage(originalImg, clearX, clearY, clearW, clearH, clearX, clearY, clearW, clearH);
// Add highlight box
ctx.strokeStyle = themeColor;
ctx.lineWidth = 3;
ctx.lineJoin = 'round';
ctx.strokeRect(clearX, clearY, clearW, clearH);
// --- Data Extraction & Parsing ---
const textLine = targetLine.text.trim();
let playerVol = "Unknown", normalizedVol = "Unknown", loudness = "Unknown";
// Parse Volumes (e.g. 100% / 100%)
const volMatch = textLine.match(/(\d+)\s*%.{1,5}?(\d+)\s*%/);
if (volMatch) {
playerVol = volMatch[1] + "%";
normalizedVol = volMatch[2] + "%";
}
// Parse Loudness (Handle multiple formats like "content loudness 3.0dB" or "(-35.0dB/-14.0dB)")
const loudMatch1 = textLine.match(/loudness\s*([+-]?[\d.]+)db/i);
const multiMatch = [...textLine.matchAll(/\(([-0-9.]+)db\s*[\/|!1l]\s*([-0-9.]+)db\)/gi)];
const loudMatch2 = textLine.match(/\(([-]?[0-9.]+)db/i);
if (loudMatch1) {
loudness = loudMatch1[1] + " dB";
} else if (multiMatch.length > 0) {
loudness = multiMatch[0][1] + " dB"; // Takes the primary measured loudness
} else if (loudMatch2) {
loudness = loudMatch2[1] + " dB";
}
// Parse Codecs
let codecs = [];
if (/opus/i.test(textLine)) codecs.push("Opus");
if (/ac3|ec-3/i.test(textLine)) codecs.push("AC3/E-AC3");
if (/mp4a|aac/i.test(textLine)) codecs.push("AAC");
if (/vorbis/i.test(textLine)) codecs.push("Vorbis");
const codecStr = codecs.length > 0 ? codecs.join(", ") : "N/A";
// Formulate intelligent explanation
let explanation = 'If "Normalized" is 100%, YouTube isn\'t turning down the volume of the video.';
const loudNum = parseFloat(loudness);
if (!isNaN(loudNum)) {
if (loudNum > 0) {
explanation = `This video's content loudness is <strong>${loudness}</strong> above YouTube's target (-14 LUFS). YouTube heavily reduces player volume to prevent it from being unexpectedly loud.`;
} else if (loudNum < 0) {
explanation = `This video's content loudness is <strong>${Math.abs(loudNum)} dB</strong> below YouTube's normal target (-14 LUFS). YouTube does not dynamically boost quiet videos over 100%, so this video may sound quiet.`;
} else {
explanation = `Perfect! This video's content loudness perfectly matches YouTube's target loudness (-14 LUFS). No volume reduction is required.`;
}
}
// Update Panel UI
statusPanel.style.background = '#ffffff';
statusPanel.style.boxShadow = '0 6px 16px rgba(0,0,0,0.06)';
statusPanel.style.border = 'none';
statusPanel.style.borderLeft = `6px solid ${themeColor}`;
const boxStyle = 'flex: 1; min-width: 140px; background: #f8f9fa; padding: 12px 16px; border-radius: 6px; border: 1px solid #e9ecef;';
const labelStyle = 'font-size: 11px; color: #6c757d; font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase; margin-bottom: 5px;';
const valueStyle = 'font-size: 22px; font-weight: 800; color: #212529;';
statusPanel.innerHTML = `
<h3 style="margin: 0 0 12px 0; font-size: 19px; font-weight: 700; color: #111;">Volume Normalization Analysis</h3>
<div style="font-size: 14px; color: #495057; background: #f1f3f5; padding: 10px 14px; border-radius: 6px; font-family: monospace; overflow-wrap: break-word;">
${textLine}
</div>
<div style="display: flex; flex-wrap: wrap; gap: 12px; margin-top: 20px;">
<div style="${boxStyle}">
<div style="${labelStyle}">Player Volume</div>
<div style="${valueStyle}">${playerVol}</div>
</div>
<div style="${boxStyle}">
<div style="${labelStyle}">Normalized</div>
<div style="${valueStyle}">${normalizedVol}</div>
</div>
<div style="${boxStyle}">
<div style="${labelStyle}">Loudness</div>
<div style="${valueStyle}">${loudness}</div>
</div>
<div style="${boxStyle}">
<div style="${labelStyle}">Codecs Detected</div>
<div style="${valueStyle}">${codecStr}</div>
</div>
</div>
<div style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #dee2e6; font-size: 14.5px; line-height: 1.6; color: #343a40;">
<strong style="color: ${themeColor}">Insight:</strong> ${explanation}
</div>
`;
} catch (err) {
statusPanel.innerHTML = `
<div style="text-align:center; color:#b91c1c;">
<h3 style="margin:0 0 10px 0;">Analysis Error</h3>
<p style="margin:0;">${err.message}</p>
</div>
`;
}
})();
// The container is immediately returned to display the loading state while OCR resolves.
return container;
}
Apply Changes