Skip to main content
Glama

YouTube MCP Server

by kevinwatt
metadata.ts8.42 kB
import type { Config } from "../config.js"; import { _spawnPromise, validateUrl } from "./utils.js"; /** * Video metadata interface containing all fields that can be extracted */ export interface VideoMetadata { // Basic video information id?: string; title?: string; fulltitle?: string; description?: string; alt_title?: string; display_id?: string; // Creator/uploader information uploader?: string; uploader_id?: string; uploader_url?: string; creators?: string[]; creator?: string; // Channel information channel?: string; channel_id?: string; channel_url?: string; channel_follower_count?: number; channel_is_verified?: boolean; // Timestamps and dates timestamp?: number; upload_date?: string; release_timestamp?: number; release_date?: string; release_year?: number; modified_timestamp?: number; modified_date?: string; // Video properties duration?: number; duration_string?: string; view_count?: number; concurrent_view_count?: number; like_count?: number; dislike_count?: number; repost_count?: number; average_rating?: number; comment_count?: number; age_limit?: number; // Content classification live_status?: string; is_live?: boolean; was_live?: boolean; playable_in_embed?: string; availability?: string; media_type?: string; // Playlist information playlist_id?: string; playlist_title?: string; playlist?: string; playlist_count?: number; playlist_index?: number; playlist_autonumber?: number; playlist_uploader?: string; playlist_uploader_id?: string; playlist_channel?: string; playlist_channel_id?: string; // URLs and technical info webpage_url?: string; webpage_url_domain?: string; webpage_url_basename?: string; original_url?: string; filename?: string; ext?: string; // Content metadata categories?: string[]; tags?: string[]; cast?: string[]; location?: string; license?: string; // Series/episode information series?: string; series_id?: string; season?: string; season_number?: number; season_id?: string; episode?: string; episode_number?: number; episode_id?: string; // Music/track information track?: string; track_number?: number; track_id?: string; artists?: string[]; artist?: string; genres?: string[]; genre?: string; composers?: string[]; composer?: string; album?: string; album_type?: string; album_artists?: string[]; album_artist?: string; disc_number?: number; // Technical metadata extractor?: string; epoch?: number; // Additional fields that might be present [key: string]: unknown; } /** * Extract video metadata without downloading the actual video content. * Uses yt-dlp's --dump-json flag to get comprehensive metadata. * * @param url - The URL of the video to extract metadata from * @param fields - Optional array of specific fields to extract. If not provided, returns all available metadata * @param config - Configuration object (currently unused but kept for consistency) * @returns Promise resolving to formatted metadata string or JSON object * @throws {Error} When URL is invalid or metadata extraction fails * * @example * ```typescript * // Get all metadata * const metadata = await getVideoMetadata('https://youtube.com/watch?v=...'); * console.log(metadata); * * // Get specific fields only * const specificData = await getVideoMetadata( * 'https://youtube.com/watch?v=...', * ['id', 'title', 'description', 'channel'] * ); * console.log(specificData); * ``` */ export async function getVideoMetadata( url: string, fields?: string[], _config?: Config ): Promise<string> { // Validate the URL validateUrl(url); const args = [ "--dump-json", "--no-warnings", "--no-check-certificate", url ]; try { // Execute yt-dlp to get metadata const output = await _spawnPromise("yt-dlp", args); // Parse the JSON output const metadata: VideoMetadata = JSON.parse(output); // If specific fields are requested, filter the metadata if (fields !== undefined && fields.length >= 0) { const filteredMetadata: Partial<VideoMetadata> = {}; for (const field of fields) { if (metadata.hasOwnProperty(field)) { filteredMetadata[field as keyof VideoMetadata] = metadata[field as keyof VideoMetadata]; } } return JSON.stringify(filteredMetadata, null, 2); } // Return formatted JSON string with all metadata return JSON.stringify(metadata, null, 2); } catch (error) { if (error instanceof Error) { // Handle common yt-dlp errors if (error.message.includes("Video unavailable")) { throw new Error(`Video is unavailable or private: ${url}`); } else if (error.message.includes("Unsupported URL")) { throw new Error(`Unsupported URL or extractor not found: ${url}`); } else if (error.message.includes("network")) { throw new Error(`Network error while extracting metadata: ${error.message}`); } else { throw new Error(`Failed to extract video metadata: ${error.message}`); } } throw new Error(`Failed to extract video metadata from ${url}`); } } /** * Get a human-readable summary of key video metadata fields. * This is useful for quick overview without overwhelming JSON output. * * @param url - The URL of the video to extract metadata from * @param config - Configuration object (currently unused but kept for consistency) * @returns Promise resolving to a formatted summary string * @throws {Error} When URL is invalid or metadata extraction fails * * @example * ```typescript * const summary = await getVideoMetadataSummary('https://youtube.com/watch?v=...'); * console.log(summary); * // Output: * // Title: Example Video Title * // Channel: Example Channel * // Duration: 10:30 * // Views: 1,234,567 * // Upload Date: 2023-12-01 * // Description: This is an example video... * ``` */ export async function getVideoMetadataSummary( url: string, _config?: Config ): Promise<string> { // Get the full metadata first const metadataJson = await getVideoMetadata(url, undefined, _config); const metadata: VideoMetadata = JSON.parse(metadataJson); // Format key fields into a readable summary const lines: string[] = []; if (metadata.title) { lines.push(`Title: ${metadata.title}`); } if (metadata.channel) { lines.push(`Channel: ${metadata.channel}`); } if (metadata.uploader && metadata.uploader !== metadata.channel) { lines.push(`Uploader: ${metadata.uploader}`); } if (metadata.duration_string) { lines.push(`Duration: ${metadata.duration_string}`); } else if (metadata.duration) { const hours = Math.floor(metadata.duration / 3600); const minutes = Math.floor((metadata.duration % 3600) / 60); const seconds = metadata.duration % 60; const durationStr = hours > 0 ? `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}` : `${minutes}:${seconds.toString().padStart(2, '0')}`; lines.push(`Duration: ${durationStr}`); } if (metadata.view_count !== undefined) { lines.push(`Views: ${metadata.view_count.toLocaleString()}`); } if (metadata.like_count !== undefined) { lines.push(`Likes: ${metadata.like_count.toLocaleString()}`); } if (metadata.upload_date) { // Format YYYYMMDD to YYYY-MM-DD const dateStr = metadata.upload_date; if (dateStr.length === 8) { const formatted = `${dateStr.substring(0, 4)}-${dateStr.substring(4, 6)}-${dateStr.substring(6, 8)}`; lines.push(`Upload Date: ${formatted}`); } else { lines.push(`Upload Date: ${dateStr}`); } } if (metadata.live_status && metadata.live_status !== 'not_live') { lines.push(`Status: ${metadata.live_status.replace('_', ' ')}`); } if (metadata.tags && metadata.tags.length > 0) { lines.push(`Tags: ${metadata.tags.slice(0, 5).join(', ')}${metadata.tags.length > 5 ? '...' : ''}`); } if (metadata.description) { // Truncate description to first 200 characters const desc = metadata.description.length > 200 ? metadata.description.substring(0, 200) + '...' : metadata.description; lines.push(`Description: ${desc}`); } return lines.join('\n'); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kevinwatt/yt-dlp-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server