Please bookmark this page to avoid losing your image tool!

Movie Screenshot Identifier Scanner

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, themeColor = "#00ff00", scannerSpeed = 2) {
    // Parse parameters
    if (typeof scannerSpeed === 'string') scannerSpeed = parseFloat(scannerSpeed) || 2;
    if (typeof themeColor !== 'string') themeColor = "#00ff00";

    // Main container
    const container = document.createElement('div');
    container.style.position = 'relative';
    container.style.fontFamily = 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif';
    container.style.display = 'inline-block';
    container.style.maxWidth = '100%';
    container.style.borderRadius = '8px';
    container.style.overflow = 'hidden';
    container.style.boxShadow = '0 10px 25px rgba(0,0,0,0.3)';
    container.style.background = '#111';

    // Image canvas
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = originalImg.width;
    canvas.height = originalImg.height;
    ctx.drawImage(originalImg, 0, 0);
    canvas.style.display = 'block';
    canvas.style.maxWidth = '100%';
    canvas.style.height = 'auto';

    container.appendChild(canvas);

    // Scanner Overlay
    const overlay = document.createElement('div');
    overlay.style.position = 'absolute';
    overlay.style.top = '0';
    overlay.style.left = '0';
    overlay.style.width = '100%';
    overlay.style.height = '100%';
    overlay.style.background = 'rgba(0, 0, 0, 0.3)';
    overlay.style.display = 'flex';
    overlay.style.flexDirection = 'column';
    overlay.style.alignItems = 'center';
    overlay.style.justifyContent = 'center';
    overlay.style.color = '#fff';
    overlay.style.zIndex = '10';
    overlay.style.transition = 'all 0.5s ease';

    // Scan Line
    const scanLine = document.createElement('div');
    scanLine.style.position = 'absolute';
    scanLine.style.top = '0';
    scanLine.style.left = '0';
    scanLine.style.width = '100%';
    scanLine.style.height = '3px';
    scanLine.style.backgroundColor = themeColor;
    scanLine.style.boxShadow = `0 0 15px ${themeColor}, 0 0 30px ${themeColor}`;
    scanLine.style.zIndex = '11';
    overlay.appendChild(scanLine);

    // Status Text Badge
    const statusText = document.createElement('div');
    statusText.innerText = 'Initializing Scanner...';
    statusText.style.fontSize = 'clamp(14px, 3vw, 20px)';
    statusText.style.fontWeight = 'bold';
    statusText.style.color = '#fff';
    statusText.style.textShadow = '0 2px 4px rgba(0,0,0,0.8)';
    statusText.style.background = 'rgba(0,0,0,0.6)';
    statusText.style.padding = '10px 20px';
    statusText.style.borderRadius = '30px';
    statusText.style.backdropFilter = 'blur(4px)';
    statusText.style.border = `1px solid ${themeColor}`;
    statusText.style.boxShadow = `0 0 10px rgba(0,0,0,0.5)`;
    overlay.appendChild(statusText);

    container.appendChild(overlay);

    // Scanner Animation Loop
    let pos = 0;
    let dir = 1;
    let isScanning = true;
    let lastTime = 0;

    const animate = (time) => {
        if (!isScanning) return;
        if (!lastTime) lastTime = time;
        let delta = time - lastTime;
        if (delta > 100) delta = 16; // limit jump on inactive tabs
        lastTime = time;

        pos += dir * scannerSpeed * (delta / 16);
        if (pos >= 100) {
            pos = 100;
            dir = -1;
        } else if (pos <= 0) {
            pos = 0;
            dir = 1;
        }
        scanLine.style.top = `${pos}%`;
        requestAnimationFrame(animate);
    };
    requestAnimationFrame(animate);

    // Process API requests asynchronously
    (async () => {
        try {
            // Compress large images to ensure fast API upload
            let maxSize = 800; // max dimension for trace.moe
            let uploadCanvas = canvas;
            
            if (canvas.width > maxSize || canvas.height > maxSize) {
                statusText.innerText = 'Optimizing Image...';
                uploadCanvas = document.createElement('canvas');
                let ratio = Math.min(maxSize / canvas.width, maxSize / canvas.height);
                uploadCanvas.width = canvas.width * ratio;
                uploadCanvas.height = canvas.height * ratio;
                uploadCanvas.getContext('2d').drawImage(canvas, 0, 0, uploadCanvas.width, uploadCanvas.height);
            }

            statusText.innerText = 'Scanning visual database...';
            
            const blob = await new Promise(resolve => uploadCanvas.toBlob(resolve, 'image/jpeg', 0.8));
            const formData = new FormData();
            formData.append('image', blob);

            // Fetch match from trace.moe (Public Anime Screenshot Database)
            const response = await fetch('https://api.trace.moe/search', {
                method: 'POST',
                body: formData
            });
            const data = await response.json();

            isScanning = false;
            scanLine.style.display = 'none';

            if (data && data.result && data.result.length > 0) {
                const bestMatch = data.result[0];
                const similarity = (bestMatch.similarity * 100).toFixed(1);
                
                if (bestMatch.similarity < 0.75) {
                    showError(`No confident match found. (Highest Likelihood: ${similarity}%)`);
                    return;
                }

                statusText.innerText = 'Match found! Fetching metadata...';
                
                // Fetch rich metadata using Anilist GraphQL API
                const query = `
                query ($id: Int) {
                  Media (id: $id, type: ANIME) {
                    title {
                      romaji
                      english
                      native
                    }
                    coverImage {
                      large
                    }
                    format
                  }
                }
                `;
                
                const variables = { id: bestMatch.anilist };
                let title = bestMatch.filename;
                let coverUrl = '';
                let format = '';

                try {
                    const aniRes = await fetch('https://graphql.anilist.co', {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json',
                            'Accept': 'application/json',
                        },
                        body: JSON.stringify({ query, variables })
                    });
                    const aniData = await aniRes.json();
                    if (aniData && aniData.data && aniData.data.Media) {
                        const media = aniData.data.Media;
                        const titles = media.title;
                        title = titles.english || titles.romaji || titles.native;
                        coverUrl = media.coverImage.large;
                        format = media.format || 'Unknown';
                    }
                } catch (e) {
                    console.error('Anilist API error', e);
                }

                // Prepare results UI
                overlay.style.background = 'rgba(0, 0, 0, 0.85)';
                overlay.style.backdropFilter = 'blur(8px)';
                
                const resultDiv = document.createElement('div');
                resultDiv.style.textAlign = 'center';
                resultDiv.style.width = '90%';
                resultDiv.style.maxWidth = '450px';
                resultDiv.style.background = 'rgba(255,255,255,0.08)';
                resultDiv.style.padding = '20px';
                resultDiv.style.borderRadius = '12px';
                resultDiv.style.boxShadow = '0 8px 32px rgba(0,0,0,0.5)';
                resultDiv.style.border = '1px solid rgba(255,255,255,0.1)';
                resultDiv.style.animation = 'fadeIn 0.4s ease';
                resultDiv.style.maxHeight = '90%';
                resultDiv.style.overflowY = 'auto';

                const closeBtn = document.createElement('button');
                closeBtn.innerHTML = '&times;';
                closeBtn.style.position = 'absolute';
                closeBtn.style.top = '10px';
                closeBtn.style.right = '15px';
                closeBtn.style.background = 'transparent';
                closeBtn.style.border = 'none';
                closeBtn.style.color = '#fff';
                closeBtn.style.fontSize = '32px';
                closeBtn.style.cursor = 'pointer';
                closeBtn.style.textShadow = '0 2px 5px rgba(0,0,0,0.5)';
                closeBtn.onclick = () => { overlay.style.display = 'none'; };

                resultDiv.innerHTML = `
                    <style>
                        @keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
                    </style>
                    <h2 style="margin: 0 0 15px 0; color: ${themeColor}; font-size: 22px; text-transform: uppercase; letter-spacing: 1px;">Target Identified</h2>
                    ${coverUrl ? `<img src="${coverUrl}" style="max-height: 140px; border-radius: 8px; margin-bottom: 15px; box-shadow: 0 4px 10px rgba(0,0,0,0.6);" />` : ''}
                    <div style="font-size: 20px; margin-bottom: 15px; font-weight: bold; color: #fff; line-height: 1.3;">${escapeHtml(title)}</div>
                    
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; text-align: left; background: rgba(0,0,0,0.4); padding: 15px; border-radius: 8px;">
                        <div style="font-size: 13px; color: #bbb;">Match Confidence</div>
                        <div style="font-size: 14px; font-weight: bold; color: ${similarity > 90 ? '#00ff00' : '#ffa500'}; text-align: right;">${similarity}%</div>
                        
                        <div style="font-size: 13px; color: #bbb;">Format</div>
                        <div style="font-size: 14px; font-weight: bold; color: #fff; text-align: right;">${escapeHtml(format)}</div>

                        <div style="font-size: 13px; color: #bbb;">Episode Section</div>
                        <div style="font-size: 14px; font-weight: bold; color: #fff; text-align: right;">${bestMatch.episode || 'Movie/OVA'}</div>
                        
                        <div style="font-size: 13px; color: #bbb;">Scene Timestamp</div>
                        <div style="font-size: 14px; font-weight: bold; color: #fff; text-align: right;">${formatTime(bestMatch.from)}</div>
                    </div>
                `;

                overlay.innerHTML = '';
                overlay.appendChild(closeBtn);
                overlay.appendChild(resultDiv);

                // Option to display the matching scene video clip
                if (bestMatch.video) {
                    const videoContainer = document.createElement('div');
                    videoContainer.style.marginTop = '15px';
                    videoContainer.style.width = '100%';
                    videoContainer.style.borderRadius = '8px';
                    videoContainer.style.overflow = 'hidden';
                    videoContainer.style.background = '#000';
                    const video = document.createElement('video');
                    video.src = bestMatch.video;
                    video.controls = true;
                    video.autoplay = true;
                    video.muted = true;
                    video.loop = true;
                    video.style.width = '100%';
                    video.style.display = 'block';
                    videoContainer.appendChild(video);
                    resultDiv.appendChild(videoContainer);
                }

            } else {
                showError('No references found in the visual database.');
            }
        } catch (error) {
            isScanning = false;
            scanLine.style.display = 'none';
            showError('Network error or Scanner API unavailable.');
            console.error(error);
        }
    })();

    // Helper to display error/warnings cleanly
    function showError(msg) {
        overlay.innerHTML = '';
        const errorDiv = document.createElement('div');
        errorDiv.style.background = 'rgba(220, 50, 50, 0.9)';
        errorDiv.style.padding = '20px';
        errorDiv.style.borderRadius = '8px';
        errorDiv.style.fontWeight = 'bold';
        errorDiv.style.textAlign = 'center';
        errorDiv.style.boxShadow = '0 6px 20px rgba(0,0,0,0.6)';
        errorDiv.style.maxWidth = '300px';
        
        const text = document.createElement('div');
        text.innerText = msg;
        text.style.fontSize = '16px';
        
        const subtext = document.createElement('div');
        subtext.innerText = 'Note: This open-source scanner uses trace.moe which specifically focuses on indexed animated movies & television shows.';
        subtext.style.fontSize = '12px';
        subtext.style.marginTop = '12px';
        subtext.style.fontWeight = 'normal';
        subtext.style.color = '#ffd';
        subtext.style.lineHeight = '1.4';

        const btn = document.createElement('button');
        btn.innerText = 'Dismiss';
        btn.style.marginTop = '15px';
        btn.style.padding = '8px 20px';
        btn.style.border = 'none';
        btn.style.borderRadius = '20px';
        btn.style.cursor = 'pointer';
        btn.style.background = '#fff';
        btn.style.color = '#333';
        btn.style.fontWeight = 'bold';
        btn.style.transition = 'background 0.2s';
        btn.onmouseover = () => btn.style.background = '#ddd';
        btn.onmouseout = () => btn.style.background = '#fff';
        btn.onclick = () => { overlay.style.display = 'none'; };

        errorDiv.appendChild(text);
        errorDiv.appendChild(subtext);
        errorDiv.appendChild(btn);
        overlay.appendChild(errorDiv);
        overlay.style.background = 'rgba(0,0,0,0.6)';
    }

    // Helper functions
    function escapeHtml(unsafe) {
        return (unsafe || "").toString()
             .replace(/&/g, "&amp;")
             .replace(/</g, "&lt;")
             .replace(/>/g, "&gt;")
             .replace(/"/g, "&quot;")
             .replace(/'/g, "&#039;");
    }

    function formatTime(seconds) {
        if (!seconds) return '00:00';
        const m = Math.floor(seconds / 60);
        const s = Math.floor(seconds % 60);
        return `${m}:${s < 10 ? '0' : ''}${s}`;
    }

    return container;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

This tool identifies the source of anime screenshots by scanning visual databases. When an image is uploaded, the scanner analyzes the frame to find matches and retrieves detailed metadata, including the series title, format, episode information, and specific scene timestamps. It can also provide a video clip of the identified scene and show the match confidence level. This tool is useful for anime fans looking to identify a specific show or scene from a single frame, or for content creators needing to verify the origins of animation clips.

Leave a Reply

Your email address will not be published. Required fields are marked *