Thumbnail Generation Guide

Learn how to generate high-quality, representative thumbnails from your videos using VideoIntel's intelligent frame scoring algorithm.

Overview

VideoIntel uses advanced computer vision techniques to analyze every frame and select the most visually appealing and representative moments for thumbnails. Unlike simple time-based sampling, our algorithm considers:

  • Visual Quality: Sharpness, contrast, and clarity
  • Content Diversity: Avoids similar-looking frames
  • Face Detection: Prefers frames with visible faces (coming soon)
  • Color Distribution: Balanced and vibrant colors
  • Motion Blur: Avoids blurry frames

How It Works

The thumbnail generation process follows a sophisticated 7-step pipeline:

Step 1: Extraction Strategy

Calculate which frames to extract as candidates. Extracts 3x more candidates than needed (e.g., 15 candidates for 5 thumbnails) and skips 5% margins at start/end to avoid fade-in/fade-out transitions.

Step 2: Frame Extraction

Extract candidate frames at calculated timestamps. Frames are extracted in chronological order for optimal performance with minimum 2-second spacing between candidates.

Step 3: Quality Scoring

Score each frame using a composite algorithm that evaluates: sharpness (Laplacian variance), brightness (luminance formula), contrast (min-max range), and color variance (RGB diversity). Scores are normalized to 0-1 range.

Step 4: Frame Filtering

Filter out unusable frames: black frames (<5% brightness), white/overexposed frames (>95% brightness), and blurry frames (<5% sharpness). Typically filters 20-30% of candidates.

Step 5: Diversity Filter

Apply temporal diversity to spread thumbnails across video timeline. Always selects highest-scoring frame, then adds frames that are at least 5 seconds apart for comprehensive video coverage.

Step 6: Thumbnail Generation

Convert selected frames to final thumbnails with specified format (JPEG/PNG), quality (0-1), and dimensions. Maintains aspect ratio automatically if only one dimension specified.

Step 7: Memory Cleanup

Clean up temporary canvas elements to prevent memory leaks. Essential for processing multiple videos or generating many thumbnails.

Quality Scoring Algorithm

Each frame receives a composite quality score based on four metrics:

// Simplified scoring algorithm
function scoreFrame(imageData: ImageData) {
  // 1. Sharpness (0-1): Laplacian variance edge detection
  // Sharp images have strong edges and high variance
  const sharpness = calculateLaplacianVariance(imageData);
  
  // 2. Brightness (0-1): Perceptual luminance
  // Y = 0.299R + 0.587G + 0.114B (weighted for human perception)
  const brightness = calculatePerceptualBrightness(imageData);
  
  // 3. Contrast (0-1): Dynamic range
  // High contrast = wide range of brightness values
  const contrast = (maxLuminance - minLuminance) / 255;
  
  // 4. Color Variance (0-1): RGB diversity
  // Colorful images have high variance across channels
  const colorVariance = calculateRGBVariance(imageData);
  
  // Composite score with weighted average
  const score = (
    sharpness * 0.4 +      // 40% weight (most important)
    brightness * 0.2 +     // 20% weight
    contrast * 0.2 +       // 20% weight
    colorVariance * 0.2    // 20% weight
  );
  
  return score;
}

Frame Filtering Thresholds

VideoIntel uses conservative thresholds to filter unusable frames:

CheckThresholdPurpose
Black Frame< 5% brightnessFilter fade-to-black transitions
White Frame> 95% brightnessFilter overexposed/fade-to-white
Blurry Frame< 5% sharpnessFilter out-of-focus or motion blur
import videoIntel from 'videointel';

// Generate 10 high-quality thumbnails
const thumbnails = await videoIntel.getThumbnails(videoFile, {
  count: 10,
  quality: 0.9,
  width: 1280,
  height: 720,
});

// Thumbnails are automatically sorted by quality (best first)
const bestThumbnail = thumbnails[0];
console.log(`Best thumbnail at ${bestThumbnail.timestamp}s`);
console.log(`Quality score: ${bestThumbnail.quality}`);

Best Practices

Choose the Right Count

The optimal number of thumbnails depends on your use case:

// For video previews (single thumbnail)
const thumbnails = await videoIntel.getThumbnails(file, { count: 1 });

// For video galleries (3-5 thumbnails)
const thumbnails = await videoIntel.getThumbnails(file, { count: 5 });

// For video seekbar/timeline (10-20 thumbnails)
const thumbnails = await videoIntel.getThumbnails(file, { count: 15 });

// For comprehensive analysis (30+ thumbnails)
const thumbnails = await videoIntel.getThumbnails(file, { count: 50 });

💡 Performance Tip

More thumbnails = longer processing time. For a 10-second video: 5 thumbnails ≈ 2s, 20 thumbnails ≈ 5s. Balance quality with user experience.

Set Quality Appropriately

The quality parameter affects both file size and visual fidelity:

// Low quality - Small file size, good for previews (0.5-0.6)
const lowQuality = await videoIntel.getThumbnails(file, { 
  quality: 0.6,
  width: 640,
  height: 360 
});

// Medium quality - Balanced (0.7-0.8) ⭐ Recommended
const mediumQuality = await videoIntel.getThumbnails(file, { 
  quality: 0.8,
  width: 1280,
  height: 720 
});

// High quality - Large files, best visual quality (0.9-1.0)
const highQuality = await videoIntel.getThumbnails(file, { 
  quality: 0.95,
  width: 1920,
  height: 1080 
});

Quality Optimization

Resize for Target Display

Always resize thumbnails to match your display size. This reduces memory usage and improves performance:

// For mobile thumbnails
const mobileThumbs = await videoIntel.getThumbnails(file, {
  width: 320,
  height: 180,
  quality: 0.7,
});

// For desktop gallery
const desktopThumbs = await videoIntel.getThumbnails(file, {
  width: 640,
  height: 360,
  quality: 0.8,
});

// For full-screen hero images
const heroThumbs = await videoIntel.getThumbnails(file, {
  width: 1920,
  height: 1080,
  quality: 0.9,
});

Maintain Aspect Ratio

VideoIntel automatically maintains the source video's aspect ratio:

// Specify only width - height calculated automatically
const thumbnails = await videoIntel.getThumbnails(file, {
  width: 1280,
  // height will be calculated based on video aspect ratio
});

// Or specify both for exact dimensions (may crop or pad)
const thumbnails = await videoIntel.getThumbnails(file, {
  width: 1280,
  height: 720,  // Forces 16:9 aspect ratio
});

Performance Tips

Use Appropriate Sample Sizes

For long videos, you don't need to analyze every second:

// For short videos (< 1 min) - High frequency sampling
const shortVideo = await videoIntel.getThumbnails(file, {
  count: 10,  // Dense sampling for detailed analysis
});

// For medium videos (1-10 min) - Medium frequency
const mediumVideo = await videoIntel.getThumbnails(file, {
  count: 15,  // Good balance
});

// For long videos (> 10 min) - Strategic sampling
const longVideo = await videoIntel.getThumbnails(file, {
  count: 20,  // Sufficient coverage without overhead
});

Process in Batches

When generating thumbnails for multiple videos, process them sequentially to avoid memory issues:

async function processManyVideos(files: File[]) {
  const allThumbnails = [];
  
  for (const file of files) {
    const thumbnails = await videoIntel.getThumbnails(file, {
      count: 5,
      quality: 0.8,
    });
    
    allThumbnails.push({ file: file.name, thumbnails });
    
    // Optional: Small delay to prevent overwhelming the browser
    await new Promise(resolve => setTimeout(resolve, 100));
  }
  
  return allThumbnails;
}

// Usage
const results = await processManyVideos(videoFiles);

Common Use Cases

Video Gallery Thumbnail

Generate a single, high-quality representative thumbnail:

async function createGalleryThumbnail(videoFile: File) {
  const thumbnails = await videoIntel.getThumbnails(videoFile, {
    count: 1,
    quality: 0.85,
    width: 640,
    height: 360,
  });
  
  // Use the best thumbnail
  const thumbnail = thumbnails[0];
  
  // Create img element
  const img = document.createElement('img');
  img.src = thumbnail.dataUrl;
  img.alt = `Video thumbnail at ${thumbnail.timestamp}s`;
  img.className = 'rounded-lg shadow-lg';
  
  return img;
}

Video Timeline/Scrubber

Create preview thumbnails for video timeline navigation:

async function createTimelineThumbnails(videoFile: File, videoElement: HTMLVideoElement) {
  const duration = videoElement.duration;
  const thumbnails = await videoIntel.getThumbnails(videoFile, {
    count: 20,
    quality: 0.7,
    width: 160,
    height: 90,
  });
  
  // Create hover preview
  const previewContainer = document.createElement('div');
  previewContainer.className = 'timeline-preview';
  
  videoElement.addEventListener('mousemove', (e) => {
    const rect = videoElement.getBoundingClientRect();
    const position = (e.clientX - rect.left) / rect.width;
    const time = position * duration;
    
    // Find closest thumbnail
    const closest = thumbnails.reduce((prev, curr) => 
      Math.abs(curr.timestamp - time) < Math.abs(prev.timestamp - time) 
        ? curr 
        : prev
    );
    
    // Show preview
    previewContainer.innerHTML = `
      <img src="${closest.dataUrl}" alt="Preview">
      <span>${formatTime(closest.timestamp)}</span>
    `;
  });
}

Chapter Selection

Generate thumbnails for video chapters or sections:

async function createChapterThumbnails(videoFile: File) {
  // First detect scenes (natural chapter breaks)
  const scenes = await videoIntel.detectScenes(videoFile);
  
  // Then generate thumbnails
  const thumbnails = await videoIntel.getThumbnails(videoFile, {
    count: scenes.length,
    quality: 0.85,
  });
  
  // Match thumbnails to scenes
  const chapters = scenes.map((scene, i) => ({
    title: `Chapter ${i + 1}`,
    timestamp: scene.timestamp,
    thumbnail: thumbnails[i].dataUrl,
  }));
  
  return chapters;
}

🚀 Next Steps