Skip to main content
Glama

mcp-google-sheets

generate-video-from-image.ts9.72 kB
import { createAction, Property, DynamicPropsValue } from '@activepieces/pieces-framework'; import { propsValidation } from '@activepieces/pieces-common'; import { runwayAuth } from '../common'; import RunwayML from '@runwayml/sdk'; import { z } from 'zod'; export const generateVideoFromImage = createAction({ auth: runwayAuth, name: 'generate_video_from_image', displayName: 'Generate Video From Image', description: 'Generates a video based on image(s) and text prompt using Runway\'s AI models', props: { model: Property.StaticDropdown({ displayName: 'Model', description: 'AI model to use for video generation', required: true, options: { options: [ { label: 'Gen4 Turbo (Fast, High Quality)', value: 'gen4_turbo' }, { label: 'Gen3a Turbo (Balanced)', value: 'gen3a_turbo' }, { label: 'Veo3 (Google\'s Latest)', value: 'veo3' }, ], }, }), promptImageFile: Property.File({ displayName: 'Prompt Image File', description: 'Upload an image file to use as the video\'s starting frame', required: false }), promptImageUrl: Property.ShortText({ displayName: 'Prompt Image URL', description: 'HTTPS URL of an image to use as the video\'s starting frame', required: false }), imagePosition: Property.StaticDropdown({ displayName: 'Image Position', description: 'Position of the image in the video (last frame only supported by gen3a_turbo)', required: false, defaultValue: 'first', options: { options: [ { label: 'First Frame', value: 'first' }, { label: 'Last Frame (gen3a_turbo only)', value: 'last' }, ], }, }), promptText: Property.LongText({ displayName: 'Prompt Text (Optional)', description: 'Describe what should happen in the video (max 1000 characters)', required: false }), ratio: Property.DynamicProperties({ displayName: 'Video Resolution', description: 'Available resolutions depend on the selected model', required: true, refreshers: ['model'], props: async ({ model }) => { const ratioOptions: DynamicPropsValue = {}; if ((model as unknown as string) === 'gen4_turbo') { ratioOptions['ratio'] = Property.StaticDropdown({ displayName: 'Resolution', required: true, options: { options: [ { label: 'HD Landscape (1280x720)', value: '1280:720' }, { label: 'HD Portrait (720x1280)', value: '720:1280' }, { label: 'Wide (1104x832)', value: '1104:832' }, { label: 'Tall (832x1104)', value: '832:1104' }, { label: 'Square (960x960)', value: '960:960' }, { label: 'Ultra Wide (1584x672)', value: '1584:672' }, ], }, }); } else if ((model as unknown as string) === 'gen3a_turbo') { ratioOptions['ratio'] = Property.StaticDropdown({ displayName: 'Resolution', required: true, options: { options: [ { label: 'Landscape (1280x768)', value: '1280:768' }, { label: 'Portrait (768x1280)', value: '768:1280' }, ], }, }); } else if ((model as unknown as string) === 'veo3') { ratioOptions['ratio'] = Property.StaticDropdown({ displayName: 'Resolution', required: true, options: { options: [ { label: 'HD Landscape (1280x720)', value: '1280:720' }, { label: 'HD Portrait (720x1280)', value: '720:1280' }, ], }, }); } else { ratioOptions['ratio'] = Property.StaticDropdown({ displayName: 'Resolution', required: true, options: { options: [ { label: 'Select a model first', value: '' }, ], }, }); } return ratioOptions; }, }), duration: Property.DynamicProperties({ displayName: 'Video Duration', description: 'Available durations depend on the selected model', required: true, refreshers: ['model'], props: async ({ model }) => { const durationOptions: DynamicPropsValue = {}; if ((model as unknown as string) === 'veo3') { durationOptions['duration'] = Property.StaticDropdown({ displayName: 'Duration (seconds)', required: true, defaultValue: 8, options: { options: [ { label: '8 seconds (required for veo3)', value: 8 }, ], }, }); } else if ((model as unknown as string) === 'gen4_turbo' || (model as unknown as string) === 'gen3a_turbo') { durationOptions['duration'] = Property.StaticDropdown({ displayName: 'Duration (seconds)', required: true, options: { options: [ { label: '5 seconds', value: 5 }, { label: '10 seconds', value: 10 }, ], }, }); } else { durationOptions['duration'] = Property.StaticDropdown({ displayName: 'Duration (seconds)', required: true, options: { options: [ { label: 'Select a model first', value: '' }, ], }, }); } return durationOptions; }, }), seed: Property.Number({ displayName: 'Seed (Optional)', description: 'Random seed for reproducible results (0-4294967295)', required: false }), contentModeration: Property.DynamicProperties({ displayName: 'Content Moderation', description: 'Content moderation settings (not available for veo3)', required: false, refreshers: ['model'], props: async ({ model }) => { const moderationOptions: DynamicPropsValue = {}; if ((model as unknown as string) === 'veo3') { moderationOptions['info'] = Property.MarkDown({ value: '**Note:** Content moderation is not supported by the veo3 model.', }); } else { moderationOptions['publicFigureThreshold'] = Property.StaticDropdown({ displayName: 'Public Figure Detection', description: 'How strict content moderation should be for recognizable public figures', required: false, defaultValue: 'auto', options: { options: [ { label: 'Auto (Recommended)', value: 'auto' }, { label: 'Low (Less Strict)', value: 'low' }, ], }, }); } return moderationOptions; }, }), }, async run({ auth, propsValue, files }) { // Zod validation await propsValidation.validateZod(propsValue, { promptText: z.string().max(1000, 'Prompt text must be 1000 characters or fewer').optional(), seed: z.number().min(0, 'Seed must be at least 0').max(4294967295, 'Seed must be at most 4294967295').optional(), promptImageUrl: z.string().url('Prompt image URL must be a valid HTTPS URL').optional(), }); // Input validation const hasFile = !!propsValue.promptImageFile; const hasUrl = !!propsValue.promptImageUrl; if ((hasFile && hasUrl) || (!hasFile && !hasUrl)) { throw new Error('You must provide either a Prompt Image File or a Prompt Image URL, but not both.'); } // Model-specific validations const model = propsValue.model; const duration = (propsValue.duration as any)?.['duration'] || propsValue.duration; const ratio = (propsValue.ratio as any)?.['ratio'] || propsValue.ratio; const imagePosition = propsValue.imagePosition || 'first'; // Validate duration per model if (model === 'veo3' && duration !== 8) { throw new Error('veo3 model requires a duration of exactly 8 seconds'); } if ((model === 'gen4_turbo' || model === 'gen3a_turbo') && duration !== 5 && duration !== 10) { throw new Error(`${model} model requires a duration of either 5 or 10 seconds`); } // Validate ratio per model const validRatios = { gen4_turbo: ['1280:720', '720:1280', '1104:832', '832:1104', '960:960', '1584:672'], gen3a_turbo: ['1280:768', '768:1280'], veo3: ['1280:720', '720:1280'] }; if (!validRatios[model as keyof typeof validRatios]?.includes(ratio)) { throw new Error(`Invalid resolution ${ratio} for model ${model}`); } // Validate image position per model if (imagePosition === 'last' && model !== 'gen3a_turbo') { throw new Error('Last frame positioning is only supported by gen3a_turbo model'); } // Prepare image URL let imageUrl: string; if (hasFile) { const f = propsValue.promptImageFile as any; const ext = f?.extension || 'jpeg'; imageUrl = `data:image/${ext};base64,${f?.base64}`; } else { imageUrl = propsValue.promptImageUrl as string; } const apiKey = auth as string; const client = new RunwayML({ apiKey }); // Build request body according to SDK specification const requestBody: any = { model: model, ratio: ratio, }; // Handle different image input formats based on position if (imagePosition === 'first' || model !== 'gen3a_turbo') { // Simple string format for first frame or non-gen3a_turbo models requestBody.promptImage = imageUrl; } else { // Array format for last frame positioning (gen3a_turbo only) requestBody.promptImage = [{ uri: imageUrl, position: imagePosition }]; } // Add optional parameters if (duration) { requestBody.duration = duration; } if (propsValue.promptText) { requestBody.promptText = propsValue.promptText; } if (propsValue.seed !== undefined) { requestBody.seed = propsValue.seed; } // Add content moderation only for supported models if (model !== 'veo3' && (propsValue.contentModeration as any)?.['publicFigureThreshold']) { requestBody.contentModeration = { publicFigureThreshold: (propsValue.contentModeration as any)['publicFigureThreshold'] }; } try { const task = await client.imageToVideo.create(requestBody); return { success: true, taskId: task.id, }; } catch (error: any) { throw new Error(`Failed to generate video: ${error.message || 'Unknown error'}`); } }, });

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/activepieces/activepieces'

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