---
title: inputs
---

<div class="python-reference">
<a id="fastvideo.eval.io.inputs-classes"></a>
<a id="fastvideo.eval.io.inputs-functions"></a>
<a id="fastvideo.eval.io.inputs.samples_from--parameters"></a>
<a id="fastvideo.eval.io.inputs.samples_from--returns"></a>
<a id="fastvideo.eval.io.inputs.samples_from--cardinality-and-shape"></a>
<a id="fastvideo.eval.io.inputs.samples_from--notes"></a>
<a id="fastvideo.eval.io.inputs"></a>
<p>Input-shape helpers: paths → samples list.</p>
<p>The :class:<code>Evaluator</code> and every metric consume the same internal
representation — a <code>list[dict]</code> with one dict per sample.  Building
that list by hand is the largest source of ceremony in user scripts.
:func:<code>samples_from</code> is a pure function that turns path-style inputs
(generated videos / audio, optional paired references, optional
per-sample prompts / fps / metadata) into the canonical samples list,
ready to hand to :meth:<code>Evaluator.evaluate</code>.</p>
<p>Design rules:</p>
<ul>
<li>No Evaluator state, no metric introspection, no I/O beyond reading
  the prompts file when given.  Just dict assembly.</li>
<li>"Extra" keys on a sample are free — metrics each read what they need
  and ignore the rest.  So one fat samples list naturally serves many
  metrics in one Evaluator.</li>
<li>No "primary modality" axis.  Each modality is a named kwarg
  (<code>video=</code>, <code>audio=</code>); pass whichever you have.  Both is fine.</li>
<li>No <code>mode</code> axis.  The shape of the output is determined by
  cardinality: when <code>|gen| == |ref|</code> the samples list is pair-zipped;
  when <code>|gen| &lt; |ref|</code> the unmatched references become role-tagged
  set samples for corpus-shaped metrics (FVD / FAD) without disturbing
  per-sample paired metrics (LPIPS / PSNR / SSIM / gt_optical_flow).</li>
</ul>
<h2 id="fastvideo.eval.io.inputs.Video"><code>fastvideo.eval.io.inputs.Video</code></h2>
<pre class="doc-signature"><code>Video(source: Any, fps: float | None = None, frames: Any = None, audio: Any = None, audio_sr: int | None = None)</code></pre>
<p>Path-backed media handle. The :class:<code>VideoPool</code> populates
<code>frames</code> (and optionally <code>audio</code>) before the metric loop sees
the sample.</p>

<h3 id="fastvideo.eval.io.inputs.Video.has_frames"><code>fastvideo.eval.io.inputs.Video.has_frames</code></h3>
<pre class="doc-signature"><code>has_frames() -&gt; bool</code></pre>

<h3 id="fastvideo.eval.io.inputs.Video.has_audio"><code>fastvideo.eval.io.inputs.Video.has_audio</code></h3>
<pre class="doc-signature"><code>has_audio() -&gt; bool</code></pre>

<h2 id="fastvideo.eval.io.inputs.as_video"><code>fastvideo.eval.io.inputs.as_video</code></h2>
<pre class="doc-signature"><code>as_video(x: str | Path | torch.Tensor | Video) -&gt; Video</code></pre>
<p>Coerce path/tensor/Video → :class:<code>Video</code> for the pool to decode.</p>
<p>Path strings and :class:<code>pathlib.Path</code> become <code>Video(source=str(x))</code>;
the pool then calls :func:<code>load_video</code> on first use.  Tensors become
<code>Video(source=None, frames=x)</code> — the pool sees <code>.frames</code> already
populated and forwards untouched.  :class:<code>Video</code> instances pass through.</p>
<details><summary>Source</summary><pre><code>def as_video(x: str | Path | torch.Tensor | Video) -&gt; Video:
    &quot;&quot;&quot;Coerce path/tensor/Video → :class:`Video` for the pool to decode.

    Path strings and :class:`pathlib.Path` become ``Video(source=str(x))``;
    the pool then calls :func:`load_video` on first use.  Tensors become
    ``Video(source=None, frames=x)`` — the pool sees ``.frames`` already
    populated and forwards untouched.  :class:`Video` instances pass through.
    &quot;&quot;&quot;
    if isinstance(x, Video):
        return x
    if isinstance(x, str | Path):
        return Video(source=str(x))
    if isinstance(x, torch.Tensor):
        return Video(source=None, frames=x)
    raise TypeError(f&quot;Cannot coerce &#123;type(x).__name__&#125; to Video&quot;)</code></pre></details>
<h2 id="fastvideo.eval.io.inputs.samples_from"><code>fastvideo.eval.io.inputs.samples_from</code></h2>
<pre class="doc-signature"><code>samples_from(*, video: PathSpec | None = None, reference: PathSpec | None = None, audio: PathSpec | None = None, reference_audio: PathSpec | None = None, text_prompt: str | None = None, text_prompts: str | Path | list&#91;str&#93; | None = None, fps: float | None = None, auxiliary_info: dict | list&#91;dict&#93; | None = None, extras: dict | list&#91;dict&#93; | None = None, extract_audio: bool | str | Path = False, extract_workers: int = 4) -&gt; list&#91;dict&#93;</code></pre>
<p>Build a samples list from path-style inputs.</p>
<h2>Parameters</h2>
<p>video, reference, audio, reference_audio :
    File path, directory of files (sorted by name), or any iterable
    of paths.  Pass whichever modalities apply to the metrics you
    plan to run — they attach to <code>sample["video"]</code> /
    <code>sample["reference"]</code> / <code>sample["audio"]</code> /
    <code>sample["reference_audio"]</code> respectively.  Video paths are
    wrapped in :class:<code>Video</code> so :class:<code>VideoPool</code> decodes them
    lazily in parallel; audio paths stay as strings (audio metrics
    each load with their own resample / preprocess).
text_prompt :
    A single prompt string broadcast onto every sample.
text_prompts :
    A list of strings (one per sample), or a path to a <code>.jsonl</code> /
    <code>.json</code> file containing per-sample prompts.
fps :
    Scalar fps broadcast onto every sample.
auxiliary_info :
    Single dict (broadcast) or list of dicts (zipped) for
    <code>sample["auxiliary_info"]</code> — vbench structured-prompt
    metrics read this.
extras :
    Catch-all per-sample attachments.  Use for metric-specific keys
    the dedicated kwargs don't cover (<code>scenario</code>, <code>view</code>,
    <code>actions</code>, <code>calibration</code>, <code>reference_take2</code>, ...).  Pass
    a single dict to broadcast or a list-of-dicts to zip; the keys
    merge into each sample dict.
extract_audio :
    If truthy, auto-extract audio from each <code>video</code> /
    <code>reference</code> source into <code>.wav</code> files via PyAV and attach
    the paths under <code>sample["audio"]</code> / <code>sample["reference_audio"]</code>.
    Pass a path for a persistent cache, <code>True</code> for a tempdir.
    Skipped silently for videos with no audio stream; ignored
    wherever <code>audio</code> / <code>reference_audio</code> is already explicit.
extract_workers :
    Parallel workers for <code>extract_audio</code>.</p>
<h2>Returns</h2>
<p>list[dict]
    Canonical samples shape — hand directly to
    :meth:<code>Evaluator.evaluate</code>.</p>
<h2>Cardinality and shape</h2>
<p>Let <code>N = len(generated inputs)</code> (the agreed length of whichever
of <code>video</code> / <code>audio</code> you passed).  References are attached
1:1 onto the first N samples; any extras (when <code>|ref| &gt; N</code>)
become standalone role-tagged samples at the end of the list, so
set metrics like FVD see the full reference corpus while per-sample
paired metrics like LPIPS only run on the first N pairs.</p>
<h2>Notes</h2>
<p>"Missing" keys are simply absent from the sample dict.  Metrics
handle them per their own contract (<code>sample.get(...)</code> for
optional, <code>sample[...]</code> raises for required, or
:meth:<code>BaseMetric._skip</code> for opt-in skip behavior).  One fat samples
list with many keys can serve many metrics — each reads its subset.</p>
<details><summary>Source</summary><pre><code>def samples_from(
    *,
    # Modality inputs — pass whichever you have, in any combination.
    video: PathSpec | None = None,
    reference: PathSpec | None = None,
    audio: PathSpec | None = None,
    reference_audio: PathSpec | None = None,
    # Per-sample attachments.  Scalars broadcast to every sample; lists
    # / jsonl paths zip per-sample.
    text_prompt: str | None = None,
    text_prompts: str | Path | list&#91;str&#93; | None = None,
    fps: float | None = None,
    auxiliary_info: dict | list&#91;dict&#93; | None = None,
    # Catch-all for exotic metric inputs (physics_iq scenarios,
    # synthetic_optical_flow actions, ...).  Single dict broadcasts;
    # list of dicts zips.  Keys merge into each sample dict.
    extras: dict | list&#91;dict&#93; | None = None,
    # Sugar: pull audio off the video sources via PyAV.  Pass a path
    # to use a persistent on-disk cache, ``True`` for a system tempdir.
    extract_audio: bool | str | Path = False,
    extract_workers: int = 4,
) -&gt; list&#91;dict&#93;:
    &quot;&quot;&quot;Build a samples list from path-style inputs.

    Parameters
    ----------
    video, reference, audio, reference_audio :
        File path, directory of files (sorted by name), or any iterable
        of paths.  Pass whichever modalities apply to the metrics you
        plan to run — they attach to ``sample&#91;&quot;video&quot;&#93;`` /
        ``sample&#91;&quot;reference&quot;&#93;`` / ``sample&#91;&quot;audio&quot;&#93;`` /
        ``sample&#91;&quot;reference_audio&quot;&#93;`` respectively.  Video paths are
        wrapped in :class:`Video` so :class:`VideoPool` decodes them
        lazily in parallel; audio paths stay as strings (audio metrics
        each load with their own resample / preprocess).
    text_prompt :
        A single prompt string broadcast onto every sample.
    text_prompts :
        A list of strings (one per sample), or a path to a ``.jsonl`` /
        ``.json`` file containing per-sample prompts.
    fps :
        Scalar fps broadcast onto every sample.
    auxiliary_info :
        Single dict (broadcast) or list of dicts (zipped) for
        ``sample&#91;&quot;auxiliary_info&quot;&#93;`` — vbench structured-prompt
        metrics read this.
    extras :
        Catch-all per-sample attachments.  Use for metric-specific keys
        the dedicated kwargs don&#x27;t cover (``scenario``, ``view``,
        ``actions``, ``calibration``, ``reference_take2``, ...).  Pass
        a single dict to broadcast or a list-of-dicts to zip; the keys
        merge into each sample dict.
    extract_audio :
        If truthy, auto-extract audio from each ``video`` /
        ``reference`` source into ``.wav`` files via PyAV and attach
        the paths under ``sample&#91;&quot;audio&quot;&#93;`` / ``sample&#91;&quot;reference_audio&quot;&#93;``.
        Pass a path for a persistent cache, ``True`` for a tempdir.
        Skipped silently for videos with no audio stream; ignored
        wherever ``audio`` / ``reference_audio`` is already explicit.
    extract_workers :
        Parallel workers for ``extract_audio``.

    Returns
    -------
    list&#91;dict&#93;
        Canonical samples shape — hand directly to
        :meth:`Evaluator.evaluate`.

    Cardinality and shape
    ---------------------
    Let ``N = len(generated inputs)`` (the agreed length of whichever
    of ``video`` / ``audio`` you passed).  References are attached
    1:1 onto the first N samples; any extras (when ``|ref| &gt; N``)
    become standalone role-tagged samples at the end of the list, so
    set metrics like FVD see the full reference corpus while per-sample
    paired metrics like LPIPS only run on the first N pairs.

    Notes
    -----
    &quot;Missing&quot; keys are simply absent from the sample dict.  Metrics
    handle them per their own contract (``sample.get(...)`` for
    optional, ``sample&#91;...&#93;`` raises for required, or
    :meth:`BaseMetric._skip` for opt-in skip behavior).  One fat samples
    list with many keys can serve many metrics — each reads its subset.
    &quot;&quot;&quot;
    gen_video = _expand(video, _VIDEO_EXTS) if video is not None else None
    gen_audio = _expand(audio, _AUDIO_EXTS) if audio is not None else None
    ref_video = _expand(reference, _VIDEO_EXTS) if reference is not None else None
    ref_audio = _expand(reference_audio, _AUDIO_EXTS) if reference_audio is not None else None

    if gen_video is None and gen_audio is None:
        raise ValueError(&quot;samples_from: pass at least one of video= or audio=&quot;)

    # All &quot;generated&quot; inputs must agree on length — they describe the
    # same N samples in different modalities.
    n_candidates = &#91;len(x) for x in (gen_video, gen_audio) if x is not None&#93;
    if len(&#123;*n_candidates&#125;) != 1:
        raise ValueError(f&quot;samples_from: generated inputs have inconsistent lengths &#123;n_candidates&#125;&quot;)
    n = n_candidates&#91;0&#93;

    prompts = _broadcast(text_prompts if text_prompts is not None else text_prompt, n, loader=_load_prompts)
    aux = _broadcast(auxiliary_info, n)
    extras_per_sample = _broadcast(extras, n)
    if (text_prompts is not None) and (text_prompt is not None):
        raise ValueError(&quot;Pass either text_prompt (broadcast) or text_prompts (per-sample), not both.&quot;)

    samples: list&#91;dict&#91;str, Any&#93;&#93; = &#91;&#93;
    for i in range(n):
        s: dict&#91;str, Any&#93; = &#123;&#125;
        if gen_video is not None:
            s&#91;&quot;video&quot;&#93; = as_video(gen_video&#91;i&#93;)
        if gen_audio is not None:
            s&#91;&quot;audio&quot;&#93; = str(gen_audio&#91;i&#93;)
        if ref_video is not None and i &lt; len(ref_video):
            s&#91;&quot;reference&quot;&#93; = as_video(ref_video&#91;i&#93;)
        if ref_audio is not None and i &lt; len(ref_audio):
            s&#91;&quot;reference_audio&quot;&#93; = str(ref_audio&#91;i&#93;)
        if prompts is not None:
            s&#91;&quot;text_prompt&quot;&#93; = prompts&#91;i&#93;
        if fps is not None:
            s&#91;&quot;fps&quot;&#93; = fps
        if aux is not None:
            s&#91;&quot;auxiliary_info&quot;&#93; = aux&#91;i&#93;
        if extras_per_sample is not None:
            s.update(extras_per_sample&#91;i&#93;)
        samples.append(s)

    # Unmatched references → role-tagged set samples.  Per-sample paired
    # metrics skip these via the worker&#x27;s role-skip rule; set metrics
    # (FVD, FAD) accumulate the features.
    if ref_video is not None and len(ref_video) &gt; n:
        for v in ref_video&#91;n:&#93;:
            samples.append(&#123;&quot;video&quot;: as_video(v), &quot;role&quot;: &quot;reference&quot;&#125;)
    if ref_audio is not None and len(ref_audio) &gt; n:
        for a in ref_audio&#91;n:&#93;:
            samples.append(&#123;&quot;audio&quot;: str(a), &quot;role&quot;: &quot;reference&quot;&#125;)

    if extract_audio:
        _attach_extracted_audio(samples, extract_audio, extract_workers)
    return samples</code></pre></details>
<h2 id="fastvideo.eval.io.inputs.samples_from_manifest"><code>fastvideo.eval.io.inputs.samples_from_manifest</code></h2>
<pre class="doc-signature"><code>samples_from_manifest(manifest: str | Path, *, extract_audio: bool | str | Path = False, extract_workers: int = 4) -&gt; list&#91;dict&#93;</code></pre>
<p>Load canonical eval samples from a JSON or JSONL manifest.</p>
<p>Each row may contain <code>video</code>, <code>audio</code>, <code>reference</code>,
<code>reference_audio</code>, <code>reference_audio_source</code>, <code>text_prompt</code> and
arbitrary metric metadata. <code>reference_audio_source</code> is a media file
whose audio track is extracted when <code>extract_audio</code> is enabled. Paths are
resolved relative to the manifest directory. Video paths are wrapped as
:class:<code>Video</code> handles for lazy decoding; all other keys are preserved.
This is the recommended interface for benchmark generation, where matching
by sample id is safer than independently sorting folders.</p>
<details><summary>Source</summary><pre><code>def samples_from_manifest(
    manifest: str | Path,
    *,
    extract_audio: bool | str | Path = False,
    extract_workers: int = 4,
) -&gt; list&#91;dict&#93;:
    &quot;&quot;&quot;Load canonical eval samples from a JSON or JSONL manifest.

    Each row may contain ``video``, ``audio``, ``reference``,
    ``reference_audio``, ``reference_audio_source``, ``text_prompt`` and
    arbitrary metric metadata. ``reference_audio_source`` is a media file
    whose audio track is extracted when ``extract_audio`` is enabled. Paths are
    resolved relative to the manifest directory. Video paths are wrapped as
    :class:`Video` handles for lazy decoding; all other keys are preserved.
    This is the recommended interface for benchmark generation, where matching
    by sample id is safer than independently sorting folders.
    &quot;&quot;&quot;
    path = Path(manifest).expanduser().resolve()
    if not path.is_file():
        raise FileNotFoundError(path)
    if path.suffix == &quot;.jsonl&quot;:
        rows = &#91;json.loads(line) for line in path.read_text(encoding=&quot;utf-8&quot;).splitlines() if line.strip()&#93;
    elif path.suffix == &quot;.json&quot;:
        rows = json.loads(path.read_text(encoding=&quot;utf-8&quot;))
        if not isinstance(rows, list):
            raise ValueError(f&quot;&#123;path&#125;: expected a top-level JSON list&quot;)
    else:
        raise ValueError(f&quot;&#123;path&#125;: eval manifest must end in .json or .jsonl&quot;)

    samples: list&#91;dict&#91;str, Any&#93;&#93; = &#91;&#93;
    for index, row in enumerate(rows):
        if not isinstance(row, dict):
            raise ValueError(f&quot;&#123;path&#125;: row &#123;index + 1&#125; must be an object, got &#123;type(row).__name__&#125;&quot;)
        sample = dict(row)
        for key in (
                &quot;video&quot;,
                &quot;reference&quot;,
                &quot;audio&quot;,
                &quot;reference_audio&quot;,
                &quot;reference_audio_source&quot;,
        ):
            value = sample.get(key)
            if value is None:
                continue
            if not isinstance(value, str | Path):
                raise ValueError(f&quot;&#123;path&#125;: row &#123;index + 1&#125; key &#123;key!r&#125; must be a path&quot;)
            media_path = Path(value).expanduser()
            if not media_path.is_absolute():
                media_path = path.parent / media_path
            media_path = media_path.resolve()
            sample&#91;key&#93; = as_video(media_path) if key in &#123;&quot;video&quot;, &quot;reference&quot;&#125; else str(media_path)
        if &quot;video&quot; not in sample and &quot;audio&quot; not in sample:
            raise ValueError(f&quot;&#123;path&#125;: row &#123;index + 1&#125; needs at least video or audio&quot;)
        samples.append(sample)

    if not samples:
        raise ValueError(f&quot;&#123;path&#125;: eval manifest is empty&quot;)
    if extract_audio:
        _attach_extracted_audio(samples, extract_audio, extract_workers)
    return samples</code></pre></details>
</div>
