Frame-level data
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);
});