extract_key_moments
Extract timestamps and descriptions of key moments from YouTube videos to quickly identify important sections without watching the entire content.
Instructions
Extracts key moments (timestamps and descriptions) from a given YouTube video.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| youtube_url | Yes | ||
| number_of_moments | No | Number of key moments to extract (default: 3). |
Implementation Reference
- src/index.ts:308-394 (handler)The main handler logic for the 'extract_key_moments' tool. It validates input, constructs a prompt for Gemini API to extract key moments, calls the API via callGeminiApi helper, parses the response into structured JSON with timestamps and descriptions, and returns the result or handles errors.case "extract_key_moments": { try { // Parse and validate arguments const args = ExtractKeyMomentsInputSchema.parse(request.params.arguments); const { youtube_url, number_of_moments } = args; console.error(`[INFO] Received request to extract ${number_of_moments} key moments from YouTube URL: ${youtube_url}`); // Construct the prompt for Gemini API const finalPrompt = `Please extract ${number_of_moments} key moments from this video. For each moment, provide the timestamp in MM:SS format and a brief description.`; // Call Gemini API using the helper function const moments = await callGeminiApi(finalPrompt, { mimeType: "video/youtube", fileUri: youtube_url, }); console.error(`[INFO] Successfully received raw key moments text from API.`); // Parse the raw text into a structured JSON array const structuredMoments: { timestamp: string; description: string }[] = []; // Simpler Regex to capture timestamp (group 1) and the rest (group 2) const momentRegex = /(\d{1,2}:\d{2})\s*[-–—]?\s*(.*)/; // Removed 'g' flag, process line by line // Split the response into lines and process each line const lines = moments.split('\n'); for (const line of lines) { const match = line.trim().match(momentRegex); if (match && match[2]) { // Check if match and description part exist let description = match[2].trim(); // Explicitly check for the prefix and remove using substring if (description.startsWith('** - ')) { description = description.substring(5); // Remove the first 5 characters "** - " } else if (description.startsWith('- ')) { // Also handle just "- " description = description.substring(2); } structuredMoments.push({ timestamp: match[1], // Captured "MM:SS" description: description // Cleaned description }); } else if (line.trim().length > 0) { // Handle lines that might not match the exact format but contain text // Option 1: Add as description without timestamp // structuredMoments.push({ timestamp: "N/A", description: line.trim() }); // Option 2: Log a warning and potentially skip console.warn(`[WARN] Could not parse line in key moments response: "${line.trim()}"`); } } if (structuredMoments.length === 0 && moments.trim().length > 0) { console.warn("[WARN] Failed to parse any structured moments, returning raw text instead."); // Fallback to returning raw text if parsing completely fails but text exists return { content: [{ type: "text", text: moments }], }; } console.log(`[INFO] Parsed ${structuredMoments.length} key moments.`); // Return success response with JSON stringified array return { // Content type is still text, but the content is a JSON string content: [{ type: "text", text: JSON.stringify(structuredMoments, null, 2) }], }; } catch (error: any) { console.error(`[ERROR] Failed during extract_key_moments tool execution:`, error); // Handle Zod validation errors if (error instanceof z.ZodError) { return { content: [{ type: "text", text: `Invalid input: ${JSON.stringify(error.errors)}` }], isError: true, }; } // Handle generic errors let errorMessage = `Failed to extract key moments from the video.`; if (error.message) { errorMessage += ` Details: ${error.message}`; } return { content: [{ type: "text", text: errorMessage }], isError: true, }; } }
- src/index.ts:59-62 (schema)Zod schema defining the input parameters for the extract_key_moments tool: youtube_url (required URL) and number_of_moments (optional, default 3).const ExtractKeyMomentsInputSchema = z.object({ youtube_url: z.string().url({ message: "Invalid YouTube URL provided." }), number_of_moments: z.number().int().positive().optional().default(3).describe("Number of key moments to extract (default: 3)."), });
- src/index.ts:81-85 (registration)Tool registration in the ListTools handler, including name, description, and input schema.{ name: "extract_key_moments", description: "Extracts key moments (timestamps and descriptions) from a given YouTube video.", inputSchema: zodToJsonSchema(ExtractKeyMomentsInputSchema), },
- src/index.ts:96-127 (helper)Shared helper function used by the handler to call the Gemini API with video data and prompt, handling errors.async function callGeminiApi(prompt: string, fileData: { mimeType: string; fileUri: string }): Promise<string> { try { const result = await geminiModel.generateContent([ prompt, { fileData }, ]); const response = result.response; return response.text(); } catch (error: any) { console.error(`[ERROR] Gemini API call failed:`, error); // Attempt to provide more specific error info based on message content // (Since GoogleGenerativeAIError type seems unavailable for direct check) if (error instanceof Error) { // Check for common messages indicating client-side issues (API key, quota, etc.) // This part might need refinement based on actual observed error messages. if (error.message.includes('API key') || error.message.includes('permission denied')) { throw new Error(`Authentication/Authorization Error with Gemini API: ${error.message}`); } else if (error.message.includes('quota')) { throw new Error(`Gemini API quota likely exceeded: ${error.message}`); } else if (error.message.toLowerCase().includes('invalid')) { // Generic check for invalid inputs throw new Error(`Invalid input likely provided to Gemini API: ${error.message}`); } else if (error.message.includes('500') || error.message.includes('server error') || error.message.includes('network issue')) { // Guessing based on common error patterns for server/network issues throw new Error(`Gemini API server error or network issue: ${error.message}`); } // Re-throw generic error if specific checks don't match throw new Error(`Gemini API Error: ${error.message}`); } // Re-throw if it's not an Error instance for some reason throw error; // Keep original error if not an Error instance } }