Generate previews. Not FFmpeg headaches.
VidPeek is a typed video preview generation package for Node.js and CLI. It turns media files into lightweight animated previews and thumbnails using FFmpeg/FFprobe, with safe temp handling, useful presets, overwrite protection, JSON output, and readable errors for real developer workflows.
- Typed Node.js API and scriptable CLI.
- Animated preview generation for WebP, GIF, and MP4.
- Still thumbnail generation for JPG, JPEG, PNG, and WebP.
- FFprobe metadata API for duration, dimensions, FPS, codecs, and format.
- Dry-run preview planning without writing output files.
- Presets for common preview sizes.
- Safe per-run temp directories.
- No shell command strings; FFmpeg is called with spawn argument arrays.
- Readable FFmpeg/FFprobe errors with stage, command, exit code, and stderr.
- Node.js 18+
- FFmpeg
- FFprobe
Verify FFmpeg and FFprobe:
ffmpeg -version
ffprobe -versionpnpm add vidpeeknpm install vidpeekGenerate an animated preview:
vidpeek input.mp4 --out preview.webp --preset web --overwriteGenerate a thumbnail:
vidpeek thumbnail input.mp4 --out thumb.jpg --at 25% --width 640 --overwriteProbe video metadata:
vidpeek probe input.mp4 --jsonvidpeek input.mp4 \
--out preview.webp \
--preset web \
--strategy evenly-spaced \
--clips 5 \
--clip-duration 2 \
--width 320 \
--fps 10 \
--overwriteDry-run a preview plan without generating files:
vidpeek input.mp4 --out preview.webp --preset web --dry-run --jsonCreate a thumbnail from a percentage timestamp:
vidpeek thumbnail input.mp4 --out thumb.png --at 10% --height 360 --overwritePrint readable metadata:
vidpeek probe input.mp4import { generatePreview } from "vidpeek";
const result = await generatePreview({
input: "video.mp4",
output: "preview.webp",
preset: "web",
strategy: "evenly-spaced",
clips: {
count: 5,
duration: 2,
},
width: 320,
fps: 10,
overwrite: true,
});
console.log(result.sizeBytes);import { generateThumbnail } from "vidpeek";
await generateThumbnail({
input: "video.mp4",
output: "thumb.jpg",
at: "25%",
width: 640,
overwrite: true,
});import { probeVideo } from "vidpeek";
const metadata = await probeVideo("video.mp4");
console.log(metadata.duration);
console.log(metadata.width, metadata.height);
console.log(metadata.videoCodec);import { dryRunPreview } from "vidpeek";
const plan = await dryRunPreview({
input: "video.mp4",
output: "preview.webp",
preset: "web",
});
console.log(plan.segments);Preview JSON:
{
"output": "preview.webp",
"format": "webp",
"duration": 128.4,
"segments": [{ "index": 0, "start": 6.42, "duration": 2 }],
"elapsedMs": 1842,
"sizeBytes": 384920
}Thumbnail JSON:
{
"output": "thumb.jpg",
"at": 32.1,
"elapsedMs": 312,
"sizeBytes": 42188
}Probe JSON:
{
"duration": 128.4,
"width": 1920,
"height": 1080,
"fps": 29.97,
"videoCodec": "h264",
"audioCodec": "aac",
"format": "mov,mp4,m4a,3gp,3g2,mj2"
}| Preset | Format | Width | FPS | Clips | Clip duration |
|---|---|---|---|---|---|
tiny |
webp |
240 | 8 | 4 | 1.5s |
web |
webp |
320 | 10 | 5 | 2s |
discord |
webp |
360 | 12 | 4 | 2s |
high-quality |
webp |
480 | 15 | 6 | 2s |
| Option | Type | Description |
|---|---|---|
input |
string |
Input video path. Must exist locally. |
output |
string |
Output preview or thumbnail path. Parent directories are created. |
format |
"webp" | "gif" | "mp4" |
Preview output format. Defaults from the selected preset. |
preset |
"tiny" | "web" | "discord" | "high-quality" |
Preview preset. Defaults to web. |
strategy |
"evenly-spaced" | "random" | "manual" |
Segment selection strategy. Defaults to evenly-spaced. |
clips.count |
number |
Number of clips to sample. |
clips.duration |
number |
Duration of each sampled clip in seconds. |
clips.range |
[number, number] |
Normalized sampling range. Defaults to [0.05, 0.95]. |
clips.segments |
{ start: number; duration: number }[] |
Required for manual segment selection. |
width |
number |
Output width. If height is omitted, VidPeek preserves aspect ratio. |
height |
number |
Output height. If width is omitted, VidPeek preserves aspect ratio. |
fps |
number |
Preview frames per second. |
speed |
number |
Preview speed multiplier. |
at |
number | \${number}%`` |
Thumbnail timestamp in seconds or percentage. Defaults to 10%. Values beyond duration clamp to the last valid frame. |
overwrite |
boolean |
Replace an existing output file. |
keepTemp |
boolean |
Keep temporary preview files for debugging. |
ffmpegPath |
string |
Custom FFmpeg binary path. |
ffprobePath |
string |
Custom FFprobe binary path. |
VidPeek does not replace existing output files unless you explicitly ask it to.
vidpeek input.mp4 --out preview.webpIf preview.webp already exists, this fails with a readable error. Use --overwrite to replace it:
vidpeek input.mp4 --out preview.webp --overwriteThe same behavior applies to thumbnails.
Place a sample video at benchmarks/fixtures/sample.mp4, then run:
pnpm benchThe benchmark generates tiny, web, and high-quality previews and prints elapsed time plus output file size.
Install FFmpeg and FFprobe, then verify both commands work:
ffmpeg -version
ffprobe -versionIf they are not on PATH, pass custom binary paths:
vidpeek input.mp4 --out preview.webp --ffmpeg-path /path/to/ffmpeg --ffprobe-path /path/to/ffprobeVidPeek expects a local file path. Use vidpeek probe input.mp4 --json first if you need to inspect metadata before preview generation.
Pass --overwrite when replacing files intentionally. Without it, VidPeek refuses to overwrite output.
Paths with spaces are supported because VidPeek calls FFmpeg with argument arrays:
vidpeek "benchmarks\fixtures\sample video.mp4" --out "output preview.webp" --preset web --overwriteParent output directories are created automatically:
vidpeek input.mp4 --out previews/preview.webp --overwriteVidPeek outputs standard media files you can embed anywhere your app, README, dashboard, or pipeline already displays images and video. For a quick local demo, generate a WebP preview and a thumbnail:
vidpeek input.mp4 --out preview.webp --preset web --overwrite
vidpeek thumbnail input.mp4 --out thumb.jpg --at 25% --width 640 --overwrite- Typed API for Node apps.
- CLI and library from the same core pipeline.
- Safe temp dirs.
- No shell command strings.
- Readable FFmpeg/FFprobe errors.
- Useful presets.
- JSON output for automation.
- Scene-change strategy.
- Contact sheets.
- Sprites.
- AVIF previews.
- Worker and concurrency options.
MIT