Overview

Video Directory Schema

s.videodir(dir, options?)

A directory of videos that is the whole module - the video twin of s.imagedir(). Fields elsewhere pick from it with s.video(videosModule), and uploading in such a field adds the video to the directory.

The content is a record keyed by file path (an HLS stream by its master playlist). Each entry holds what Val read off the file - mimeType, width, height and duration - and, as defaults, the description (alt), poster, start and end time, focal point (hotspot) and captions. A field that picks the video starts from those defaults and may override any of them, so a video described and trimmed once is described and trimmed everywhere it is used. One exception: a description per locale (alt as a record) stays in the directory, because the field's own alt is a single string.

Upgrading? s.videodir() replaces s.videoset({ dir, ...options }), which still works but is deprecated.

Options:
dir (first argument): "/public" | `/public/${string}`

Required. Where the videos are stored. Must start with /public. Two collections may not claim the same directory.

s.videodir("/public/val/videos")
accept: `video/${string}`

Which video types may be uploaded. Must be a type starting with video/. Defaults to video/*.

s.videodir("/public/val/videos", { accept: "video/mp4" })
stream: false | { type: "hls"; renditions?: number[]; segmentDuration?: number }

Turn every upload into an HLS stream, in the browser: one rendition per height in renditions (default 1080, 720 and 480) that fits the upload - or one at the upload's own height when it is shorter than all of them - cut into segments of segmentDuration seconds (default 6). It needs a browser with WebCodecs and an H.264 encoder, in a secure context (https or localhost); where those are missing, the original file is uploaded as it is. Off unless set. Fields that pick from this directory inherit it, and can opt out with stream: false.

s.videodir("/public/val/videos", { stream: { type: "hls" } })
alt: s.string() | s.string().nullable() | s.record(s.string(), s.string())

The schema for each entry's description. Defaults to s.string().nullable(). Use s.string() to require one, or a record to have one per locale - a per-locale description is not inherited by the fields that pick the video.

s.videodir("/public/val/videos", { alt: s.string() })
Methods:
.remote: method

Store the videos on Val's remote server instead of in your git repository. Usually what you want for video. See the remote files guide.

s.videodir("/public/val/videos").remote()
Examples:
Defining a video directory
content/videos.val.ts
import { c, s } from "../val.config";

export default c.define(
  "/content/videos.val.ts",
  s.videodir("/public/val/videos", { stream: { type: "hls" } }),
  {
    // Entries are added when you upload a video. They are keyed by file path.
    "/public/val/videos/intro_51df2/master.m3u8": {
      mimeType: "application/vnd.apple.mpegurl",
      width: 1920,
      height: 1080,
      duration: 42.5,
      alt: "The team, introducing itself",
    },
  },
);
Picking a video
content/page.val.ts
import { c, s } from "../val.config";
import videosVal from "./videos.val";

export default c.define(
  "/content/page.val.ts",
  s.object({
    intro: s.video(videosVal),
  }),
  {
    intro: {
      // The path names the video; the rest comes from the directory
      path: "/public/val/videos/intro_51df2/master.m3u8",
      // ...unless the field overrides it
      startTime: 2,
    },
  },
);