Media utilities
Read media metadata and generate thumbnails.
Two functions read media files without an editor on the screen: probeMediaFile describes a file, and generateThumbnail makes a still picture from it. Use them before an upload, or to draw a media library.
Both are exported from the package root, @layermetry/media-editor. The React entry exports generateThumbnail too. Use the browser SDK from any supported framework; these utilities do not require a mounted editor.
For framework setup and complete file-input examples, open Media utilities in your React 19, React 18, Next.js, Vue, Svelte, JavaScript, Rails, Laravel, Blazor, or Electron guide. Both video and image guides include these sections.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Media utilities</title>
</head>
<body>
<input type="file" id="file" />
<img id="poster" alt="" />
<script type="module">
import { probeMediaFile, generateThumbnail } from '@layermetry/media-editor';
document.getElementById('file').addEventListener('change', async (event) => {
const file = event.target.files[0];
const info = await probeMediaFile(file);
console.log(JSON.stringify(info));
if (info.undecodableReason) console.warn(info.undecodableReason);
const thumbnail = await generateThumbnail(file, { at: 1, maxEdge: 400 });
if (thumbnail.poster) {
document.getElementById('poster').src = URL.createObjectURL(thumbnail.poster);
console.log(`Poster ${thumbnail.width} x ${thumbnail.height}, ${thumbnail.mimeType}`);
} else {
console.warn(`No poster: ${thumbnail.reason}`);
}
});
</script>
</body>
</html>File probing
probeMediaFile(file) reads the container of a file: its type, its length, and its tracks. It reads small parts of the file and decodes nothing, so it can inspect many files without decoding their media. The amount of work depends on the container and metadata layout.
For a 6-second MP4 clip, it returns:
{
"container": "iso-bmff",
"durationSource": "moov",
"durationSec": 6,
"video": { "width": 2048, "height": 1080, "codecId": "avc1.64002a", "codec": "avc1.64002a", "fps": 25 },
"audio": { "channels": 1, "sampleRate": 48000, "codecId": "mp4a.40.2", "codec": "mp4a.40.2" },
"sizeBytes": 236687,
"bitrateBps": 315582.67,
"bitrateSource": "derived"
}| Field | Description |
|---|---|
container | The kind of file, such as iso-bmff (MP4 and MOV), avi, mp3, wav, flac, or image. unknown when it cannot tell. |
durationSec | The length in seconds. |
durationSource | How the length was found: read from the file, or estimated. Read it before you trust a length for anything precise. |
video, audio | The tracks, with size, frame rate, channels, sample rate, and codec. |
sizeBytes | The size of the file. |
undecodableReason | A sentence for the user when browsers cannot play the file. |
Each track has codecId and codec. In an MP4 or MOV file, both are the codec name that the browser's decoder uses, such as avc1.64002a or mp4a.40.2. They are not the four letters stored in the file, such as mp4a. In a WebM or MKV file, codecId is the name that the file stores, such as A_OPUS or V_VP9, and codec is present only when the file says enough to write the decoder's name.
The sound of an MP4 or MOV file can be AAC, Opus, FLAC, MP3, or PCM. probeMediaFile reports each of them, with codec set to mp4a.40.2 for plain AAC, opus, flac, or mp3. PCM is pcm-s16, pcm-s24, pcm-s32, pcm-f32, or pcm-u8 when its samples are stored the way the browser's decoder takes them. Otherwise, it is the four letters that the file uses, for example twos for 16-bit big-endian samples.
It never throws because of the contents of a file. It also describes files that browsers cannot play, such as AVI and WMV. For an AVI file, undecodableReason says: "This is an AVI file. Browsers have no decoder for the video inside it, so it cannot be played or edited here. Converting it to MP4 will fix that."
For still images, the probe reports the container and image format; dimensions come from thumbnail decoding. The absence of undecodableReason does not guarantee that a browser can decode the file.
Use it to refuse a file before the upload, with a sentence the user understands:
const info = await probeMediaFile(file);
if (info.undecodableReason) alert(info.undecodableReason);Thumbnails
generateThumbnail(file, options) makes a still picture from a video or a picture file. It uses no page and no <video> element, so it also runs in a Worker.
| Option | Default | Description |
|---|---|---|
at | 1 | The time in seconds; negative times become zero, and times past the end use the last frame. Ignored for still images. |
maxEdge | 400 | The longest edge in pixels, at least 16. A picture never grows beyond its source. |
format | 'image/jpeg' | 'image/jpeg', 'image/png', or 'image/webp'. |
quality | 0.82 | Compression from 0 to 1. Not used for PNG. |
box, fit | none, 'contain' | A fixed { width, height }, filled with 'contain' or 'cover'. |
frames | none | A positive count or { count, maxEdge } for evenly spaced video samples. Ignored for still images. |
timeoutMs | 5000 | Shared video-work deadline for the poster and sampled frames. |
signal | none | An AbortSignal for video work. Still-image decoding currently does not receive this signal. |
When frames is requested for a video, the result may contain frames and matching frameTimesSec. It can return fewer frames when the shared deadline is reached. Still images omit both fields. Ignore stale completions when a file changes or a component is disposed, including after aborting a still-image request; revoke preview object URLs on replacement and teardown.
The result has poster, a Blob, and its width, height, and mimeType. sourceWidth and sourceHeight give the size of the source as it is meant to be seen, with the rotation of a phone video already applied.
Read mimeType instead of assuming the format you asked for. Safari answers a request for WebP with a PNG.
It never fails because of a file. When there is no picture, poster is null, reason is a sentence for the user, and reasonCode is a short code for your program, such as unsupported-container, no-video-track, or timeout. Only an option that makes no sense, such as a maxEdge of -3, rejects the Promise with a RangeError.
The first call on a page takes about a second, because the browser starts its video decoder. Later calls take tens of milliseconds.
MP4, MOV, WebM, and Matroska videos give a picture when the browser can decode them, and so do JPEG, PNG, GIF, WebP, and AVIF pictures. TIFF, SVG, and HEIC files are recognized, and answered with a reason.