Docs/Skills and code/src/
src/video-info-fetch.js
Mentioned on: page 12
import { bestThumbnailUrl, formatDurationText, parseAbsoluteDate, textValue } from "./video-normalization.js";
function firstText(...values) {
for (const value of values) {
const candidate = typeof value === "number" ? String(value) : textValue(value);
if (candidate != null && candidate.trim()) return candidate.trim();
}
return null;
}
function numericOrNull(value) {
if (value == null || value === "") return null;
const number = Number(value);
return Number.isFinite(number) ? number : null;
}
function safeJson(value) {
try { return JSON.stringify(value); } catch { return null; }
}
/**
* Normalize either youtubei.js getBasicInfo() or getInfo() into our stable DB shape.
* getBasicInfo avoids parsing the watch-next response, where youtubei.js 18.1.0
* currently logs a StructuredDescriptionContent / VideoTitleHeaderView type mismatch.
*/
export function normalizeVideoInfoPayload(info, videoId, source = "youtubei_get_basic_info") {
const basic = info?.basic_info ?? {};
const primary = info?.primary_info ?? {};
const secondary = info?.secondary_info ?? {};
const owner = secondary?.owner ?? {};
const viewText = firstText(
primary?.view_count?.view_count,
primary?.view_count?.short_view_count,
primary?.view_count?.original_view_count
);
const publishedText = firstText(
basic.publish_date,
basic.upload_date,
basic.published_at,
primary?.published,
primary?.relative_date
);
const parsedDate = parseAbsoluteDate(publishedText);
const rawViewCount = numericOrNull(basic.view_count) ?? numericOrNull(primary?.view_count?.original_view_count);
const thumbnailList = basic.thumbnail ?? basic.thumbnails ?? [];
const thumbnailUrl = bestThumbnailUrl(thumbnailList);
const channelName = firstText(basic.author, owner?.title, owner?.author?.name);
const normalizedId = String(basic.id ?? videoId);
const result = {
video_id: normalizedId,
title: firstText(basic.title, primary?.title) ?? "",
url: `https://www.youtube.com/watch?v=${normalizedId}`,
channel_url: null,
channel_name: channelName,
video_channel_id: basic.channel_id ?? owner?.author?.id ?? null,
thumbnail_url: thumbnailUrl,
duration: formatDurationText(basic.duration),
duration_seconds: numericOrNull(basic.duration),
duration_precision: numericOrNull(basic.duration) == null ? "unknown" : "exact",
view_count: rawViewCount,
view_count_text: viewText ?? (rawViewCount == null ? null : String(rawViewCount)),
view_count_precision: rawViewCount == null ? "unknown" : "exact",
published_at: parsedDate.value,
published_at_text: publishedText,
published_at_precision: parsedDate.precision,
likes_count: numericOrNull(basic.like_count),
category: basic.category ?? null,
description: basic.short_description ?? null,
metadata_source: source,
raw_metadata_json: safeJson({
source,
video_id: basic.id ?? videoId,
channel_id: basic.channel_id ?? null,
title: basic.title ?? null,
duration: basic.duration ?? null,
view_count: basic.view_count ?? null,
like_count: basic.like_count ?? null,
category: basic.category ?? null,
publish_date: basic.publish_date ?? null,
upload_date: basic.upload_date ?? null,
published_text: publishedText,
relative_date_text: textValue(primary?.relative_date),
thumbnail_urls: Array.isArray(thumbnailList) ? thumbnailList.filter(item => item?.url).map(item => item.url) : [],
playability_status: info?.playability_status?.status ?? null,
basic_info_keys: Object.keys(basic),
primary_info_available: Object.keys(primary).length > 0,
secondary_info_available: Object.keys(secondary).length > 0
})
};
if (!result.title) {
const status = info?.playability_status?.status ?? "unknown";
throw new Error(`YouTube returned no usable video title for ${videoId} (playability_status=${status}; source=${source}).`);
}
return result;
}
/**
* Use the lighter player-only request by default. Opt into getInfo() only when
* secondary/watch-next metadata is specifically needed: it is slower and can
* trigger known parser mismatch warnings on YouTube's changing response shape.
*/
export async function fetchVideoInfo(youtube, videoId, { fullInfo = false } = {}) {
const method = fullInfo ? "getInfo" : "getBasicInfo";
if (typeof youtube?.[method] !== "function") {
throw new Error(`youtubei.js client does not expose ${method}().`);
}
const info = await youtube[method](videoId);
return normalizeVideoInfoPayload(info, videoId, fullInfo ? "youtubei_get_info" : "youtubei_get_basic_info");
}
Shown as it is in the repository at build time (2026-11-03); the home directory appears as <home>. If this page and the file differ, the file is right. Back to the catalogue.