You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, searchCountry = 'US', language = 'eng') {
// Escape helper to prevent XSS issues when displaying API data or recognized text
function escapeHTML(str) {
if (!str) return '';
return String(str).replace(/[&<>'"]/g, match => {
return {
'&': '&',
'<': '<',
'>': '>',
"'": ''',
'"': '"'
}[match];
});
}
// Set up the container
const container = document.createElement('div');
container.style.fontFamily = 'system-ui, -apple-system, sans-serif';
container.style.padding = '30px 20px';
container.style.boxSizing = 'border-box';
container.style.width = '100%';
container.style.minHeight = '400px';
container.style.backgroundColor = '#121212';
container.style.color = '#fff';
container.style.borderRadius = '12px';
container.style.boxShadow = '0 8px 24px rgba(0,0,0,0.5)';
container.style.overflow = 'hidden';
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.style.alignItems = 'center';
container.style.justifyContent = 'center';
// Spinner view
const spinnerSvg = `
<svg width="60" height="60" viewBox="0 0 50 50" style="margin-bottom: 20px;">
<circle cx="25" cy="25" r="20" fill="none" stroke="#007aff" stroke-width="4" stroke-dasharray="31.4 31.4" stroke-dashoffset="0">
<animateTransform attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="1s" repeatCount="indefinite" />
</circle>
</svg>
`;
const loadingText = document.createElement('div');
loadingText.innerText = "Initializing Scanner...";
loadingText.style.fontSize = '16px';
loadingText.style.fontWeight = '500';
loadingText.style.color = '#ddd';
container.innerHTML = spinnerSvg;
container.appendChild(loadingText);
// Run async process cleanly so we don't block the caller from mounting the element
(async () => {
try {
// Set limits to prevent out-of-memory errors and handle transparent images
let procW = originalImg.width;
let procH = originalImg.height;
const MAX_PROC_SIZE = 1200;
if (procW > MAX_PROC_SIZE || procH > MAX_PROC_SIZE) {
const ratio = Math.min(MAX_PROC_SIZE / procW, MAX_PROC_SIZE / procH);
procW = Math.round(procW * ratio);
procH = Math.round(procH * ratio);
}
const procCanvas = document.createElement('canvas');
procCanvas.width = procW;
procCanvas.height = procH;
const procCtx = procCanvas.getContext('2d');
procCtx.fillStyle = '#ffffff'; // Fills white in case of transparent background
procCtx.fillRect(0, 0, procW, procH);
procCtx.drawImage(originalImg, 0, 0, procW, procH);
const thumbDataUrl = procCanvas.toDataURL('image/jpeg', 0.8);
// Dynamically import Tesseract.js if not available
if (!window.Tesseract) {
loadingText.innerText = "Loading OCR resources...";
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 network connection."));
document.head.appendChild(script);
});
}
// Begin OCR Recognition
const result = await window.Tesseract.recognize(procCanvas, language, {
logger: m => {
if (m.status === 'recognizing text') {
loadingText.innerText = `Scanning Poster... ${Math.round(m.progress * 100)}%`;
}
}
});
// Extract valid lines and prioritize the largest bounding boxes
const lines = result.data.lines;
let validLines = (lines || []).filter(l => {
const cleanText = l.text.trim().replace(/[^a-zA-Z0-9]/g, '');
return cleanText.length >= 1;
});
if (validLines.length === 0) {
throw new Error("No readable text found on the poster. Cannot identify the movie.");
}
validLines.sort((a, b) => (b.bbox.y1 - b.bbox.y0) - (a.bbox.y1 - a.bbox.y0));
let maxHeight = validLines[0].bbox.y1 - validLines[0].bbox.y0;
let titleLines = validLines.filter(l => (l.bbox.y1 - l.bbox.y0) >= maxHeight * 0.6);
titleLines.sort((a, b) => a.bbox.y0 - b.bbox.y0);
// Cleanly format text candidate
let titleCandidate = titleLines.map(l => l.text.trim()).join(' ').replace(/\s+/g, ' ').replace(/[^a-zA-Z0-9 ':!.-]/g, '');
let singleBiggestLineText = validLines[0].text.trim().replace(/\s+/g, ' ').replace(/[^a-zA-Z0-9 ':!.-]/g, '');
loadingText.innerText = `Searching for movie: "${titleCandidate}"...`;
// Query free iTunes Search API limits (No API key needed, open endpoint with fast responses)
let response = await fetch(`https://itunes.apple.com/search?term=${encodeURIComponent(titleCandidate)}&entity=movie&limit=1&country=${searchCountry}`);
if (!response.ok) throw new Error("Search API error.");
let data = await response.json();
let movie = data.results && data.results.length > 0 ? data.results[0] : null;
// Fallback attempt with just strictly the largest text line
if (!movie && titleCandidate !== singleBiggestLineText && singleBiggestLineText.length > 0) {
loadingText.innerText = `Searching fallback: "${singleBiggestLineText}"...`;
response = await fetch(`https://itunes.apple.com/search?term=${encodeURIComponent(singleBiggestLineText)}&entity=movie&limit=1&country=${searchCountry}`);
data = await response.json();
movie = data.results && data.results.length > 0 ? data.results[0] : null;
}
const titleCandidateSafe = escapeHTML(titleCandidate);
// Handle match failure
if (!movie) {
container.innerHTML = `
<div style="text-align: center; padding: 20px;">
<h3 style="color: #ff5555; margin-top: 0;">Movie Not Identified</h3>
<p style="color: #ccc; font-size: 15px;">We scanned the poster but couldn't find a matching movie in our database. Ensure the poster title is readable and unobstructed.</p>
<div style="margin-top: 20px; display: inline-block; text-align: left; background: #222; padding: 15px; border-radius: 8px; border: 1px solid #333;">
<div style="color: #888; font-size: 12px; margin-bottom: 5px; font-weight: bold; letter-spacing: 1px;">TEXT EXTRACTED</div>
<div style="color: #fff; font-size: 16px;">"${titleCandidateSafe}"</div>
</div>
<div style="margin-top: 25px;">
<img src="${thumbDataUrl}" style="max-height: 250px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.5);">
</div>
</div>
`;
container.style.justifyContent = 'flex-start';
return;
}
// Secure and structure movie data match
const titleSafe = escapeHTML(movie.trackName);
const genreSafe = escapeHTML(movie.primaryGenreName);
const descSafe = escapeHTML(movie.longDescription || movie.shortDescription || "No plot description available.");
const directorSafe = escapeHTML(movie.artistName);
const ratingSafe = escapeHTML(movie.contentAdvisoryRating || 'Unrated');
// Using a higher resolution URL provided by Apple iTunes backend format
const posterUrl = movie.artworkUrl100 ? escapeHTML(movie.artworkUrl100.replace('100x100bb', '600x600bb')) : '';
const trackUrl = escapeHTML(movie.trackViewUrl || '#');
// Construct final rendering
container.innerHTML = `
<div style="display: flex; gap: 25px; text-align: left; flex-wrap: wrap; justify-content: center; width: 100%;">
<div style="flex: 1 1 250px; max-width: 320px;">
<div style="font-size: 12px; color: #888; margin-bottom: 8px; text-align: center; font-weight: bold; letter-spacing: 1px;">SCANNED POSTER</div>
<img src="${thumbDataUrl}" style="width: 100%; border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.6);">
</div>
<div style="flex: 1 1 300px; max-width: 450px; background: #1e1e1e; padding: 25px; border-radius: 12px; border: 1px solid #2a2a2a; box-shadow: 0 8px 20px rgba(0,0,0,0.4);">
<div style="font-size: 12px; color: #4caf50; margin-bottom: 15px; font-weight: bold; text-transform: uppercase; letter-spacing: 1px;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align: text-bottom; margin-right: 4px;"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>
Match Found
</div>
<div style="display: flex; gap: 15px; align-items: flex-start; margin-bottom: 15px;">
${posterUrl ? `<img src="${posterUrl}" style="width: 100px; flex-shrink: 0; border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.5);">` : ''}
<div style="display: flex; flex-direction: column;">
<h2 style="margin: 0 0 8px 0; font-size: 24px; color: #fff; line-height: 1.25;">${titleSafe}</h2>
<div style="color: #4caf50; font-size: 14px; font-weight: 500;">
${genreSafe} • ${movie.releaseDate ? new Date(movie.releaseDate).getFullYear() : 'N/A'}
</div>
</div>
</div>
<p style="font-size: 14px; line-height: 1.6; color: #ddd; margin: 0 0 20px 0;">${descSafe}</p>
<div style="font-size: 13px; color: #999; border-top: 1px solid #333; padding-top: 15px; line-height: 1.6;">
<strong style="color: #bbb;">Director & Cast:</strong> ${directorSafe}<br>
<strong style="color: #bbb;">Content Rating:</strong> ${ratingSafe}<br>
<strong style="color: #bbb;">Matched Text:</strong> "${titleCandidateSafe}"
</div>
<a href="${trackUrl}" target="_blank" style="display: inline-block; margin-top: 20px; padding: 10px 18px; background: #007aff; color: #fff; text-decoration: none; border-radius: 6px; font-size: 14px; font-weight: 500;">
View Details on Apple TV
</a>
</div>
</div>
`;
container.style.justifyContent = 'flex-start';
} catch (e) {
container.innerHTML = `
<div style="text-align: center; padding: 40px 20px;">
<svg width="50" height="50" viewBox="0 0 24 24" fill="none" stroke="#ff5555" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-bottom: 15px;">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="12"></line>
<line x1="12" y1="16" x2="12.01" y2="16"></line>
</svg>
<h3 style="color: #ff5555; margin-top: 0; font-size: 20px;">Tool Error</h3>
<p style="color: #ccc; font-size: 15px;">${escapeHTML(e.message)}</p>
</div>
`;
container.style.justifyContent = 'center';
}
})();
return container;
}
Apply Changes