Frame-level data

Copy page

Map video timestamps and transcript windows to YouTube storyboard sheets, heatmaps, and chapters.

YouTube publishes low-resolution storyboard sheets for most non-live videos. They are useful for choosing a moment from the timeline, but they are not finished thumbnail assets: frames are commonly only 160×90 or 320×180.

The SDK returns the image URLs and grid geometry. It does not download video, decode pixels, crop frames, or upscale images.

Find a frame at a timestamp

import { storyboardFrameAt } from 'just-yt';

const storyboards = await yt.storyboards('dQw4w9WgXcQ');
const frame = storyboardFrameAt(storyboards, 86.5);

if (frame) {
  frame.sheetUrl; // public sprite-sheet URL
  frame.atSeconds; // timestamp after interval rounding
  frame.x; // crop origin within the sheet
  frame.y;
  frame.width; // actual published frame width
  frame.height;
}

The helper uses recommendedLevel when that value names a published level, then falls back to the highest-resolution level. Pass a level number explicitly when you need a specific size:

const highest = storyboards.levels.at(-1);
const frame = storyboardFrameAt(storyboards, 86.5, highest?.level);

An invalid timestamp, an unavailable level, or a timestamp beyond the final frame returns undefined. A level with an interval of zero cannot be mapped to the timeline and also returns undefined.

Fetch only the sheets around a transcript window

Read a segmented transcript first, choose a narrow window, then map that window to its distinct sheets:

import { storyboardSheetsFor } from 'just-yt';

const transcript = await yt.transcript(videoId, { segmented: true });
// Suppose the relevant transcript beat is around 14:20.
const storyboards = await yt.storyboards(videoId);
const sheets = storyboardSheetsFor(storyboards, {
  fromSeconds: 850,
  toSeconds: 880,
});

Each result contains one sheet URL and only the cells covering that inclusive window. This keeps image requests and vision-model input scoped to the relevant part of the video.

Videos without storyboards return { levels: [] }. That is not an error. Live, upcoming, and very short videos commonly have no timeline-mappable level.

Use heatmap and chapter context

A full video() lookup can also include two optional /next enrichments:

const video = await yt.video(videoId);

video.heatmap?.[0];
// { startMs, durationMs, score }

video.chapters?.[0];
// { title, startMs, thumbnails }

Heatmap scores are normalized within one video and are not comparable between videos. Chapters and heatmaps are absent when YouTube does not publish them, and they are also absent in { basic: true } mode because that mode skips /next.

Use largestThumbnail(video.thumbnails) or the same helper on a chapter’s thumbnail list to select the largest URL YouTube actually supplied. The helper does not guess filenames or make a verification request.

Effect API

The Effect service exposes the same operation:

const program = Effect.gen(function* () {
  const yt = yield* YouTubeApi;
  return yield* yt.storyboards(videoId);
});