Implementation:NVIDIA NeMo Curator PreviewStage
| Knowledge Sources | |
|---|---|
| Domains | Video Processing, Media Encoding, Preview Generation |
| Last Updated | 2026-02-14 00:00 GMT |
Overview
PreviewStage generates animated WebP preview images from video clip windows using FFmpeg, producing lightweight thumbnails for quick visual inspection of curated video content.
Description
PreviewStage extends ProcessingStage[VideoTask, VideoTask] as a Python dataclass. It iterates over each clip and its windows in a VideoTask, writing the MP4 bytes of each window to a temporary file, invoking FFmpeg with the libwebp encoder to produce a lossy animated WebP at a configurable target FPS and height, and reading the resulting bytes back into window.webp_bytes.
The stage supports configurable compression parameters: compression_level (0-6, where 0 is lossless and 6 is most lossy) and quality (0-100, where 100 is highest quality). It allocates CPU resources via Resources(cpus=num_cpus_per_worker) and uses the allocated CPU count as the FFmpeg thread count.
The stage emits warnings when the source video's framerate or resolution is below the target preview settings, indicating that preview quality may be degraded. FFmpeg errors are caught and logged without raising exceptions, allowing the pipeline to continue processing other windows.
Usage
Use PreviewStage in video curation pipelines when you need lightweight animated preview thumbnails of video clips. Place it after clip extraction stages to generate preview images that can be used for visual inspection, quality review, or content browsing without requiring full video playback.
Code Reference
Source Location
- Repository: NeMo-Curator
- File:
nemo_curator/stages/video/preview/preview.py - Lines: 1-116
Signature
@dataclass
class PreviewStage(ProcessingStage[VideoTask, VideoTask]):
target_fps: float = 1.0
target_height: int = 240
verbose: bool = False
num_cpus_per_worker: float = 4.0
compression_level: int = 6
quality: int = 50
def __post_init__(self) -> None: ...
def inputs(self) -> tuple[list[str], list[str]]: ...
def outputs(self) -> tuple[list[str], list[str]]: ...
def process(self, task: VideoTask) -> VideoTask: ...
def _generate_preview(self, window: _Window) -> None: ...
Import
from nemo_curator.stages.video.preview.preview import PreviewStage
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| target_fps | float |
No | Target frames per second for the preview animation (default: 1.0) |
| target_height | int |
No | Target height in pixels for the preview, width is scaled proportionally (default: 240) |
| verbose | bool |
No | Enable verbose output (default: False) |
| num_cpus_per_worker | float |
No | Number of CPUs allocated per worker, also used as FFmpeg thread count (default: 4.0) |
| compression_level | int |
No | WebP compression level from 0 (lossless) to 6 (most lossy) (default: 6) |
| quality | int |
No | WebP quality from 0 (worst) to 100 (best) (default: 50) |
| task | VideoTask |
Yes | Video task containing clips with windows that have MP4 bytes to convert |
Outputs
| Name | Type | Description |
|---|---|---|
| result | VideoTask |
The same video task with webp_bytes populated on each processed window
|
Key Implementation Details
Processing Loop
The process() method iterates over all clips and their windows, generating a preview for each:
def process(self, task: VideoTask) -> VideoTask:
video: Video = task.data
if video.metadata.framerate < self.target_fps:
logger.warning(f"Video {video.input_video} has framerate {video.metadata.framerate} < {self.target_fps}, preview generation quality will be degraded.")
if video.metadata.height < self.target_height:
logger.warning(f"Video {video.input_video} has height {video.metadata.height} < {self.target_height}, preview generation quality will be degraded.")
for clip in video.clips:
for window in clip.windows:
self._generate_preview(window)
return task
FFmpeg Command Construction
The _generate_preview method constructs an FFmpeg command with the following key parameters:
command = [
"ffmpeg",
"-threads", str(int(self.resources.cpus)),
"-y",
"-i", input_mp4.as_posix(),
"-loglevel", "error",
"-vf", f"fps={self.target_fps},scale=-1:{self.target_height}",
"-c:v", "libwebp",
"-lossless", str(0),
"-compression_level", str(self.compression_level),
"-q:v", str(self.quality),
"-loop", "0",
output_webp.as_posix(),
]
Key FFmpeg settings:
- -vf applies a filter chain: first resamples to the target FPS, then scales height while preserving aspect ratio (
-1for width) - -c:v libwebp uses the WebP video codec for animated output
- -lossless 0 forces lossy encoding
- -loop 0 creates an infinitely looping animation
Temporary File Management
The stage uses make_pipeline_temporary_dir to create a temporary directory for each preview generation operation. Input MP4 bytes are written to disk, FFmpeg processes them, and the output WebP bytes are read back, all within a context manager that handles cleanup:
with make_pipeline_temporary_dir(sub_dir="preview") as tmp_dir:
input_mp4 = pathlib.Path(tmp_dir, "input.mp4")
input_mp4.write_bytes(window.mp4_bytes)
output_webp = pathlib.Path(tmp_dir, "output.webp")
# ... run FFmpeg ...
window.webp_bytes = output_webp.read_bytes()
Error Handling
FFmpeg failures are caught and logged without raising exceptions, allowing the pipeline to continue processing remaining windows:
try:
output = subprocess.check_output(command, stderr=subprocess.STDOUT)
if output:
logger.warning(f"FFmpeg output: {output.decode('utf-8')}")
except subprocess.CalledProcessError as e:
logger.error(f"FFmpeg command failed with return code {e.returncode}")
if e.output:
logger.warning(f"FFmpeg output: {e.output.decode('utf-8')}")
return
Usage Examples
Basic Usage
from nemo_curator.stages.video.preview.preview import PreviewStage
preview_stage = PreviewStage(
target_fps=1.0,
target_height=240,
quality=50,
)
High-Quality Previews
from nemo_curator.stages.video.preview.preview import PreviewStage
preview_stage = PreviewStage(
target_fps=2.0,
target_height=480,
compression_level=3,
quality=80,
num_cpus_per_worker=8.0,
)
Related Pages
- Environment:NVIDIA_NeMo_Curator_Python_Linux_Base
- NVIDIA_NeMo_Curator_ClipFrameExtractionStage - Upstream clip extraction stage
- NVIDIA_NeMo_Curator_TransNetV2ClipExtractionStage - Alternative clip extraction approach
- NVIDIA_NeMo_Curator_VideoReaderStage - Video reading stage that provides input VideoTask data