Skip to main content

Extracting a thumbnail from a video in JavaScript

Extracting a single frame (thumbnail) from a video file can be done using Mediabunny.

Here's an extractThumbnail() function you can copy and paste into your project:

import {ALL_FORMATS, Input, InputDisposedError, UrlSource, VideoSample, VideoSampleSink} from 'mediabunny';

export type ExtractThumbnailProps = {
  src: string;
  timestampInSeconds: number;
  signal?: AbortSignal;
};

export async function extractThumbnail({src, timestampInSeconds, signal}: ExtractThumbnailProps): Promise<VideoSample> {
  using input = new Input({
    formats: ALL_FORMATS,
    source: new UrlSource(src),
  });

  const videoTrack = await input.getPrimaryVideoTrack();
  if (!videoTrack) {
    throw new Error('No video track found in the input');
  }
  if (signal?.aborted) {
    throw new Error('Aborted');
  }

  const sink = new VideoSampleSink(videoTrack);
  const sample = await sink.getSample(timestampInSeconds);

  if (!sample) {
    throw new Error(`No frame found at timestamp ${timestampInSeconds}s`);
  }

  return sample;
}

Example

Here is how you can draw a thumbnail to a canvas:

const sample = await extractThumbnail({
  src: 'https://remotion.media/video.mp4',
  timestampInSeconds: 5,
});

const canvas = document.createElement('canvas');
canvas.width = sample.displayWidth;
canvas.height = sample.displayHeight;
const ctx = canvas.getContext('2d');
sample.draw(ctx!, 0, 0);
sample.close();

Memory management

The function returns a VideoSample object. When it gets cleaned up by garbage collection, it will be automatically closed, but a warning will be printed.

You can call .close() to explicitly close the sample and prevent the warning from being printed.

const sample = await extractThumbnail({
  src: 'https://example.com/video.mp4',
  timestampInSeconds: 5,
});

sample.draw(ctx!, 0, 0);
sample.close();

Or, you can use the using statement to clean up the sample when it goes out of scope.

using sample = await extractThumbnail({
  src: 'https://example.com/video.mp4',
  timestampInSeconds: 5,
});

sample.draw(ctx!, 0, 0);

Abort frame extraction

Pass an AbortSignal to cancel thumbnail extraction:

const controller = new AbortController();

setTimeout(() => controller.abort(), 5000);

try {
  using sample = await extractThumbnail({
    src: 'https://example.com/video.mp4',
    timestampInSeconds: 10,
    signal: controller.signal,
  });

  console.log('Got frame!');
} catch (error) {
  console.error('Thumbnail extraction was aborted or failed:', error);
}

Setting a timeout

Here is how you can set a maximum duration for extracting a thumbnail:

const controller = new AbortController();

const timeoutPromise = new Promise<never>((_, reject) => {
  const timeoutId = setTimeout(() => {
    controller.abort();
    reject(new Error('Thumbnail extraction timed out after 5 seconds'));
  }, 5000);

  controller.signal.addEventListener('abort', () => clearTimeout(timeoutId), {once: true});
});

try {
  using sample = await Promise.race([
    extractThumbnail({
      src: 'https://example.com/video.mp4',
      timestampInSeconds: 10,
      signal: controller.signal,
    }),
    timeoutPromise,
  ]);

  console.log('Got frame!');
} catch (error) {
  console.error('Thumbnail extraction was aborted or failed:', error);
}

See also