Skip to main content
Glama

image-to-video

Convert static images into dynamic videos using Vidu API. Specify duration, resolution, and movement amplitude for customized video generation. Supports text prompts for enhanced creative control.

Instructions

Generate a video from an image using Vidu API

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
durationNoDuration of the output video in seconds (4 or 8)
image_urlYesURL of the image to convert to video
modelNoModel name for generationvidu2.0
movement_amplitudeNoMovement amplitude of objects in the frameauto
promptNoText prompt for video generation (max 1500 chars)
resolutionNoResolution of the output video720p
seedNoRandom seed for reproducibility

Implementation Reference

  • index.ts:81-284 (handler)
    The core handler function that performs image-to-video conversion using the Vidu API. It validates inputs based on model, starts the generation task, polls for completion (up to 5 min), and returns the video and cover URLs or errors.
    async ({ image_url, prompt, duration, model, resolution, movement_amplitude, seed, bgm, callback_url }) => { try { // Validate model-specific constraints let finalDuration = duration; let finalResolution = resolution; if (model === "viduq1") { // viduq1 only supports 5s duration and 1080p resolution finalDuration = 5; finalResolution = "1080p"; if (duration && duration !== 5) { console.warn(`Model viduq1 only supports 5s duration. Using 5s instead of ${duration}s.`); } if (resolution && resolution !== "1080p") { console.warn(`Model viduq1 only supports 1080p resolution. Using 1080p instead of ${resolution}.`); } } else { // vidu1.5 and vidu2.0 if (!duration || ![4, 8].includes(duration)) { finalDuration = 4; // Default to 4s } else { finalDuration = duration; } // Resolution constraints based on duration if (finalDuration === 4) { if (!resolution || !["360p", "720p", "1080p"].includes(resolution)) { finalResolution = "360p"; // Default for 4s } else { finalResolution = resolution; } } else if (finalDuration === 8) { finalResolution = "720p"; // Only option for 8s if (resolution && resolution !== "720p") { console.warn(`8s videos only support 720p resolution. Using 720p instead of ${resolution}.`); } } } // BGM validation const finalBgm = bgm === true && finalDuration === 4; if (bgm === true && finalDuration !== 4) { console.warn(`BGM is only supported for 4s videos. BGM will not be added for ${finalDuration}s video.`); } // Step 1: Start the generation task const startResponse = await fetch(`${VIDU_API_BASE_URL}/ent/v2/img2video`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Token ${VIDU_API_KEY}` }, body: JSON.stringify({ model, images: [image_url], prompt: prompt || "", duration: finalDuration, seed: seed !== undefined ? seed : Math.floor(Math.random() * 1000000), resolution: finalResolution, movement_amplitude, bgm: finalBgm, ...(callback_url && { callback_url }) }) }); if (!startResponse.ok) { const errorData = await startResponse.text(); return { isError: true, content: [ { type: "text", text: `Error starting video generation: ${errorData}` } ] }; } const startData = await startResponse.json() as StartResponse; const taskId = startData.task_id; // Step 2: Poll for completion let state = startData.state; let result: StatusResponse | null = null; // Add a message to indicate that we're processing let status = `Task created with ID: ${taskId}\nInitial state: ${state}\n`; status += "Waiting for processing to complete...\n"; // Maximum wait time: 5 minutes const maxPolls = 60; let pollCount = 0; while (state !== "success" && state !== "failed" && pollCount < maxPolls) { // Wait for 5 seconds before polling again await new Promise(resolve => setTimeout(resolve, 5000)); const statusResponse = await fetch(`${VIDU_API_BASE_URL}/ent/v2/tasks/${taskId}/creations`, { method: "GET", headers: { "Content-Type": "application/json", "Authorization": `Token ${VIDU_API_KEY}` } }); if (!statusResponse.ok) { const errorData = await statusResponse.text(); return { isError: true, content: [ { type: "text", text: `Error checking generation status: ${errorData}` } ] }; } const statusData = await statusResponse.json() as StatusResponse; state = statusData.state; pollCount++; status += `Current state: ${state}\n`; if (state === "success") { result = statusData; break; } else if (state === "failed") { return { isError: true, content: [ { type: "text", text: `Video generation failed: ${statusData.err_code || "Unknown error"}` } ] }; } } if (state !== "success") { return { isError: true, content: [ { type: "text", text: `Timed out waiting for video generation to complete. Last state: ${state}` } ] }; } // Format the successful result if (result && result.creations && result.creations.length > 0) { const videoUrl = result.creations[0].url; const coverUrl = result.creations[0].cover_url; const credits = result.credits; return { content: [ { type: "text", text: ` Video generation complete! Task ID: ${taskId} Status: ${state} Credits used: ${credits || 'N/A'} Video URL: ${videoUrl} Cover Image URL: ${coverUrl} Note: These URLs are valid for one hour. ` } ] }; } else { return { content: [ { type: "text", text: ` Video generation completed, but no download URLs were returned. Task ID: ${taskId} Status: ${state} ` } ] }; } } catch (error: any) { console.error("Error in image-to-video tool:", error); return { isError: true, content: [ { type: "text", text: `An unexpected error occurred: ${error.message}` } ] }; } }
  • Zod schema defining input parameters for the image-to-video tool, including image_url (required), prompt, duration, model (default vidu2.0), etc.
    { image_url: z.string().url().describe("URL of the image to convert to video"), prompt: z.string().max(1500).optional().describe("Text prompt for video generation (max 1500 chars)"), duration: z.number().int().optional().describe("Duration of the output video in seconds (model-specific)"), model: z.enum(["viduq1", "vidu1.5", "vidu2.0"]).default("vidu2.0").describe("Model name for generation"), resolution: z.enum(["360p", "720p", "1080p"]).optional().describe("Resolution of the output video (model/duration-specific)"), movement_amplitude: z.enum(["auto", "small", "medium", "large"]).default("auto").describe("Movement amplitude of objects in the frame"), seed: z.number().int().optional().describe("Random seed for reproducibility"), bgm: z.boolean().optional().describe("Add background music (4s videos only)"), callback_url: z.string().url().optional().describe("Callback URL for async notifications") },
  • index.ts:67-285 (registration)
    MCP server tool registration call for 'image-to-video', specifying name, description, input schema, and handler function.
    server.tool( "image-to-video", "Generate a video from an image using Vidu API", { image_url: z.string().url().describe("URL of the image to convert to video"), prompt: z.string().max(1500).optional().describe("Text prompt for video generation (max 1500 chars)"), duration: z.number().int().optional().describe("Duration of the output video in seconds (model-specific)"), model: z.enum(["viduq1", "vidu1.5", "vidu2.0"]).default("vidu2.0").describe("Model name for generation"), resolution: z.enum(["360p", "720p", "1080p"]).optional().describe("Resolution of the output video (model/duration-specific)"), movement_amplitude: z.enum(["auto", "small", "medium", "large"]).default("auto").describe("Movement amplitude of objects in the frame"), seed: z.number().int().optional().describe("Random seed for reproducibility"), bgm: z.boolean().optional().describe("Add background music (4s videos only)"), callback_url: z.string().url().optional().describe("Callback URL for async notifications") }, async ({ image_url, prompt, duration, model, resolution, movement_amplitude, seed, bgm, callback_url }) => { try { // Validate model-specific constraints let finalDuration = duration; let finalResolution = resolution; if (model === "viduq1") { // viduq1 only supports 5s duration and 1080p resolution finalDuration = 5; finalResolution = "1080p"; if (duration && duration !== 5) { console.warn(`Model viduq1 only supports 5s duration. Using 5s instead of ${duration}s.`); } if (resolution && resolution !== "1080p") { console.warn(`Model viduq1 only supports 1080p resolution. Using 1080p instead of ${resolution}.`); } } else { // vidu1.5 and vidu2.0 if (!duration || ![4, 8].includes(duration)) { finalDuration = 4; // Default to 4s } else { finalDuration = duration; } // Resolution constraints based on duration if (finalDuration === 4) { if (!resolution || !["360p", "720p", "1080p"].includes(resolution)) { finalResolution = "360p"; // Default for 4s } else { finalResolution = resolution; } } else if (finalDuration === 8) { finalResolution = "720p"; // Only option for 8s if (resolution && resolution !== "720p") { console.warn(`8s videos only support 720p resolution. Using 720p instead of ${resolution}.`); } } } // BGM validation const finalBgm = bgm === true && finalDuration === 4; if (bgm === true && finalDuration !== 4) { console.warn(`BGM is only supported for 4s videos. BGM will not be added for ${finalDuration}s video.`); } // Step 1: Start the generation task const startResponse = await fetch(`${VIDU_API_BASE_URL}/ent/v2/img2video`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Token ${VIDU_API_KEY}` }, body: JSON.stringify({ model, images: [image_url], prompt: prompt || "", duration: finalDuration, seed: seed !== undefined ? seed : Math.floor(Math.random() * 1000000), resolution: finalResolution, movement_amplitude, bgm: finalBgm, ...(callback_url && { callback_url }) }) }); if (!startResponse.ok) { const errorData = await startResponse.text(); return { isError: true, content: [ { type: "text", text: `Error starting video generation: ${errorData}` } ] }; } const startData = await startResponse.json() as StartResponse; const taskId = startData.task_id; // Step 2: Poll for completion let state = startData.state; let result: StatusResponse | null = null; // Add a message to indicate that we're processing let status = `Task created with ID: ${taskId}\nInitial state: ${state}\n`; status += "Waiting for processing to complete...\n"; // Maximum wait time: 5 minutes const maxPolls = 60; let pollCount = 0; while (state !== "success" && state !== "failed" && pollCount < maxPolls) { // Wait for 5 seconds before polling again await new Promise(resolve => setTimeout(resolve, 5000)); const statusResponse = await fetch(`${VIDU_API_BASE_URL}/ent/v2/tasks/${taskId}/creations`, { method: "GET", headers: { "Content-Type": "application/json", "Authorization": `Token ${VIDU_API_KEY}` } }); if (!statusResponse.ok) { const errorData = await statusResponse.text(); return { isError: true, content: [ { type: "text", text: `Error checking generation status: ${errorData}` } ] }; } const statusData = await statusResponse.json() as StatusResponse; state = statusData.state; pollCount++; status += `Current state: ${state}\n`; if (state === "success") { result = statusData; break; } else if (state === "failed") { return { isError: true, content: [ { type: "text", text: `Video generation failed: ${statusData.err_code || "Unknown error"}` } ] }; } } if (state !== "success") { return { isError: true, content: [ { type: "text", text: `Timed out waiting for video generation to complete. Last state: ${state}` } ] }; } // Format the successful result if (result && result.creations && result.creations.length > 0) { const videoUrl = result.creations[0].url; const coverUrl = result.creations[0].cover_url; const credits = result.credits; return { content: [ { type: "text", text: ` Video generation complete! Task ID: ${taskId} Status: ${state} Credits used: ${credits || 'N/A'} Video URL: ${videoUrl} Cover Image URL: ${coverUrl} Note: These URLs are valid for one hour. ` } ] }; } else { return { content: [ { type: "text", text: ` Video generation completed, but no download URLs were returned. Task ID: ${taskId} Status: ${state} ` } ] }; } } catch (error: any) { console.error("Error in image-to-video tool:", error); return { isError: true, content: [ { type: "text", text: `An unexpected error occurred: ${error.message}` } ] }; } } );
  • TypeScript interfaces defining API response shapes used within the image-to-video handler (e.g., StartResponse, StatusResponse).
    interface StartResponse { task_id: string; state: string; model: string; images: string[]; prompt: string; duration: number; seed: number; resolution: string; bgm: boolean; movement_amplitude: string; created_at: string; } interface CreationItem { id: string; url: string; cover_url: string; } interface StatusResponse { state: string; err_code?: string; credits?: number; creations?: CreationItem[]; } interface UploadResponse { id: string; put_url: string; expires_at: string; } interface FinishResponse { uri: string; }
  • Companion tool to check status of tasks created by image-to-video, referencing its task_id.
    server.tool( "check-generation-status", "Check the status of a video generation task", { task_id: z.string().describe("Task ID returned by the image-to-video tool") }, async ({ task_id }) => { try { const statusResponse = await fetch(`${VIDU_API_BASE_URL}/ent/v2/tasks/${task_id}/creations`, { method: "GET", headers: { "Content-Type": "application/json", "Authorization": `Token ${VIDU_API_KEY}` } }); if (!statusResponse.ok) { const errorData = await statusResponse.text(); return { isError: true, content: [ { type: "text", text: `Error checking generation status: ${errorData}` } ] }; } const statusData = await statusResponse.json() as StatusResponse; if (statusData.state === "success") { if (statusData.creations && statusData.creations.length > 0) { const videoUrl = statusData.creations[0].url; const coverUrl = statusData.creations[0].cover_url; const credits = statusData.credits; return { content: [ { type: "text", text: ` Generation task complete! Task ID: ${task_id} Status: ${statusData.state} Credits used: ${credits || 'N/A'} Video URL: ${videoUrl} Cover Image URL: ${coverUrl} Note: These URLs are valid for one hour. ` } ] }; } else { return { content: [ { type: "text", text: ` Generation task complete but no download URLs available. Task ID: ${task_id} Status: ${statusData.state} ` } ] }; } } else if (statusData.state === "failed") { return { isError: true, content: [ { type: "text", text: `Generation task failed with error code: ${statusData.err_code || "Unknown error"}` } ] }; } else { return { content: [ { type: "text", text: ` Generation task is still in progress. Task ID: ${task_id} Current Status: ${statusData.state} You can check again later using the same task ID. ` } ] }; } } catch (error: any) { console.error("Error in check-generation-status tool:", error); return { isError: true, content: [ { type: "text", text: `An unexpected error occurred: ${error.message}` } ] }; } } );

Other Tools

Related Tools

Latest Blog Posts

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/el-el-san/vidu-mcp-server'

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