{ "kind": "open-flow-workflow", "version": 1, "name": "Animate a Product Photo with Magic Hour", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_5d9e83cbd59f1c20": { "name": "Start manually", "kind": "start", "inputDefs": [] }, "n_46f4e878a2cf127f": { "inputs": { "model": { "kind": "value", "value": "ltx-2.5" }, "end_seconds": { "kind": "value", "value": 1 }, "resolution": { "kind": "value", "value": "480p" }, "audio": { "kind": "value", "value": false }, "name": { "kind": "value", "value": "Product animation" }, "style": { "kind": "value", "value": { "prompt": "YOUR_ANIMATION_PROMPT" } }, "assets": { "kind": "value", "value": { "image_file_path": "YOUR_IMAGE_URL_OR_ASSET_PATH" } } }, "name": "Generate the video", "kind": "task", "task": { "inputs": [ { "description": "Give your video a custom name for easy identification.", "jsonSchema": { "type": "string", "description": "Give your video a custom name for easy identification." }, "nullable": true, "handle": "name", "value": null }, { "description": "The total duration of the output video in seconds. Supported durations depend on the chosen model:\n\n* **`gemini-omni-1.1`**: any integer from 3 to 10\n* **`kling-2.6`**: 5, 10\n* **`kling-3.0`**: any integer from 3 to 15\n* **`ltx-2.5`**: any integer from 1 to 60\n* **`minimax-h3`**: any integer from 1 to 30\n* **`seedance-1.5`**: any integer from 4 to 12\n* **`seedance-2.0`**: any integer from 4 to 15\n* **`seedance-2.0-fast`**: any integer from 4 to 15\n* **`seedance-2.0-mini`**: any integer from 4 to 15\n* **`seedance-2.5`**: any integer from 4 to 30\n* **`veo3.1`**: 4, 6, 8, 16, 24, 32, 40, 48, 56\n* **`veo3.1-lite`**: 4, 6, 8, 16, 24, 32, 40, 48, 56\n* **`wan-2.2`**: 3, 4, 5, 6, 7, 8, 9, 10, 15\n* **`wan-3.0`**: 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30\n", "jsonSchema": { "type": "number", "minimum": 1, "maximum": 60, "description": "The total duration of the output video in seconds. Supported durations depend on the chosen model:\n\n* **`gemini-omni-1.1`**: any integer from 3 to 10\n* **`kling-2.6`**: 5, 10\n* **`kling-3.0`**: any integer from 3 to 15\n* **`ltx-2.5`**: any integer from 1 to 60\n* **`minimax-h3`**: any integer from 1 to 30\n* **`seedance-1.5`**: any integer from 4 to 12\n* **`seedance-2.0`**: any integer from 4 to 15\n* **`seedance-2.0-fast`**: any integer from 4 to 15\n* **`seedance-2.0-mini`**: any integer from 4 to 15\n* **`seedance-2.5`**: any integer from 4 to 30\n* **`veo3.1`**: 4, 6, 8, 16, 24, 32, 40, 48, 56\n* **`veo3.1-lite`**: 4, 6, 8, 16, 24, 32, 40, 48, 56\n* **`wan-2.2`**: 3, 4, 5, 6, 7, 8, 9, 10, 15\n* **`wan-3.0`**: 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30\n" }, "nullable": false, "handle": "end_seconds" }, { "description": "The AI model to use for video generation.\n\n* `default`: uses our currently recommended model for general use. For paid tiers, defaults to `kling-3.0`. For free tiers, it defaults to `ltx-2.5`.\n* `gemini-omni-1.1`: Best for precise short clips, first/last frames, and high-resolution output.\n* `kling-2.6`: Best for action, motion blur, and controlled camera moves.\n* `kling-3.0`: Best for cinematic stories, references, and optional audio.\n* `ltx-2.5`: Fastest for general scenes, long clips, audio, and rapid iteration.\n* `minimax-h3`: Great for reference-driven clips with native audio and longer durations.\n* `seedance-1.5`: Best for smooth, consistent motion with an end frame.\n* `seedance-2.0`: Best for reference-led clips with precise subject control.\n* `seedance-2.0-fast`: Fast reference-led clips with consistent motion and audio.\n* `seedance-2.0-mini`: Faster reference-led clips with consistent motion and audio.\n* `seedance-2.5`: Best for premium realism, detail, and natural motion.\n* `veo3.1`: Best for romantic interactions and expressive action, with realistic detail.\n* `veo3.1-lite`: Balanced realism and audio at a lower cost than Veo 3.1.\n* `wan-2.2`: Best for physical motion, action, and camera movement.\n* `wan-3.0`: High-quality video with native audio, long clips, and end-frame control.\n\nIf you specify the deprecated model value that includes the `-audio` suffix, this will be the same as included `audio` as `true`.", "jsonSchema": { "type": "string", "enum": [ "default", "minimax-h3", "kling-3.0", "ltx-2.5", "wan-2.2", "seedance-2.5", "wan-3.0", "seedance-2.0-mini", "veo3.1", "veo3.1-lite", "seedance-2.0", "kling-2.6", "gemini-omni-1.1", "seedance-2.0-fast", "seedance-1.5", "sora-2", "ltx-2.3", "ltx-2", "kling-2.5", "kling-1.6", "seedance", "kling-2.5-audio", "veo3.1-audio" ], "description": "The AI model to use for video generation.\n\n* `default`: uses our currently recommended model for general use. For paid tiers, defaults to `kling-3.0`. For free tiers, it defaults to `ltx-2.5`.\n* `gemini-omni-1.1`: Best for precise short clips, first/last frames, and high-resolution output.\n* `kling-2.6`: Best for action, motion blur, and controlled camera moves.\n* `kling-3.0`: Best for cinematic stories, references, and optional audio.\n* `ltx-2.5`: Fastest for general scenes, long clips, audio, and rapid iteration.\n* `minimax-h3`: Great for reference-driven clips with native audio and longer durations.\n* `seedance-1.5`: Best for smooth, consistent motion with an end frame.\n* `seedance-2.0`: Best for reference-led clips with precise subject control.\n* `seedance-2.0-fast`: Fast reference-led clips with consistent motion and audio.\n* `seedance-2.0-mini`: Faster reference-led clips with consistent motion and audio.\n* `seedance-2.5`: Best for premium realism, detail, and natural motion.\n* `veo3.1`: Best for romantic interactions and expressive action, with realistic detail.\n* `veo3.1-lite`: Balanced realism and audio at a lower cost than Veo 3.1.\n* `wan-2.2`: Best for physical motion, action, and camera movement.\n* `wan-3.0`: High-quality video with native audio, long clips, and end-frame control.\n\nIf you specify the deprecated model value that includes the `-audio` suffix, this will be the same as included `audio` as `true`." }, "nullable": true, "handle": "model", "value": null }, { "description": "Controls the output video resolution. Defaults to `720p` on paid tiers and `480p` on free tiers.\n\n* **`gemini-omni-1.1`**: Supports 360p, 720p, 1080p, 4k.\n* **`kling-2.6`**: Supports 720p, 1080p.\n* **`kling-3.0`**: Supports 720p, 1080p, 4k.\n* **`ltx-2.5`**: Supports 480p, 720p, 1080p.\n* **`minimax-h3`**: Supports 480p, 720p, 1080p.\n* **`seedance-1.5`**: Supports 480p, 720p, 1080p.\n* **`seedance-2.0`**: Supports 480p, 720p, 1080p, 4k.\n* **`seedance-2.0-fast`**: Supports 480p, 720p.\n* **`seedance-2.0-mini`**: Supports 480p, 720p.\n* **`seedance-2.5`**: Supports 480p, 720p, 1080p.\n* **`veo3.1`**: Supports 720p, 1080p.\n* **`veo3.1-lite`**: Supports 720p, 1080p.\n* **`wan-2.2`**: Supports 480p, 720p, 1080p.\n* **`wan-3.0`**: Supports 480p, 720p, 1080p.\n", "jsonSchema": { "type": "string", "enum": [ "360p", "480p", "720p", "1080p", "4k" ], "description": "Controls the output video resolution. Defaults to `720p` on paid tiers and `480p` on free tiers.\n\n* **`gemini-omni-1.1`**: Supports 360p, 720p, 1080p, 4k.\n* **`kling-2.6`**: Supports 720p, 1080p.\n* **`kling-3.0`**: Supports 720p, 1080p, 4k.\n* **`ltx-2.5`**: Supports 480p, 720p, 1080p.\n* **`minimax-h3`**: Supports 480p, 720p, 1080p.\n* **`seedance-1.5`**: Supports 480p, 720p, 1080p.\n* **`seedance-2.0`**: Supports 480p, 720p, 1080p, 4k.\n* **`seedance-2.0-fast`**: Supports 480p, 720p.\n* **`seedance-2.0-mini`**: Supports 480p, 720p.\n* **`seedance-2.5`**: Supports 480p, 720p, 1080p.\n* **`veo3.1`**: Supports 720p, 1080p.\n* **`veo3.1-lite`**: Supports 720p, 1080p.\n* **`wan-2.2`**: Supports 480p, 720p, 1080p.\n* **`wan-3.0`**: Supports 480p, 720p, 1080p.\n" }, "nullable": true, "handle": "resolution", "value": null }, { "description": "Whether to include audio in the video. Defaults to `false` if not specified.\n\nAudio support varies by model:\n* **`gemini-omni-1.1`**: Not supported\n* **`kling-2.6`**: Not supported\n* **`kling-3.0`**: Toggle-able: audio adds extra credits when enabled\n* **`ltx-2.5`**: Toggle-able: no additional credits for audio\n* **`minimax-h3`**: Toggle-able: no additional credits for audio\n* **`seedance-1.5`**: Toggle-able: audio adds extra credits when enabled\n* **`seedance-2.0`**: Toggle-able: no additional credits for audio\n* **`seedance-2.0-fast`**: Toggle-able: no additional credits for audio\n* **`seedance-2.0-mini`**: Toggle-able: no additional credits for audio\n* **`seedance-2.5`**: Toggle-able: no additional credits for audio\n* **`veo3.1`**: Toggle-able: audio adds extra credits when enabled\n* **`veo3.1-lite`**: Toggle-able: audio adds extra credits when enabled\n* **`wan-2.2`**: Not supported\n* **`wan-3.0`**: Toggle-able: no additional credits for audio\n", "jsonSchema": { "type": "boolean", "description": "Whether to include audio in the video. Defaults to `false` if not specified.\n\nAudio support varies by model:\n* **`gemini-omni-1.1`**: Not supported\n* **`kling-2.6`**: Not supported\n* **`kling-3.0`**: Toggle-able: audio adds extra credits when enabled\n* **`ltx-2.5`**: Toggle-able: no additional credits for audio\n* **`minimax-h3`**: Toggle-able: no additional credits for audio\n* **`seedance-1.5`**: Toggle-able: audio adds extra credits when enabled\n* **`seedance-2.0`**: Toggle-able: no additional credits for audio\n* **`seedance-2.0-fast`**: Toggle-able: no additional credits for audio\n* **`seedance-2.0-mini`**: Toggle-able: no additional credits for audio\n* **`seedance-2.5`**: Toggle-able: no additional credits for audio\n* **`veo3.1`**: Toggle-able: audio adds extra credits when enabled\n* **`veo3.1-lite`**: Toggle-able: audio adds extra credits when enabled\n* **`wan-2.2`**: Not supported\n* **`wan-3.0`**: Toggle-able: no additional credits for audio\n" }, "nullable": true, "handle": "audio", "value": null }, { "description": "Attributed used to dictate the style of the output", "jsonSchema": { "type": "object", "properties": { "prompt": { "type": "string", "description": "The prompt used for the video." } }, "additionalProperties": true, "description": "Attributed used to dictate the style of the output" }, "nullable": true, "handle": "style", "value": null }, { "description": "Provide the assets for image-to-video.", "jsonSchema": { "type": "object", "properties": { "image_file_path": { "type": "string", "minLength": 1, "description": "The path of the image file. This value is either\n- a direct URL to the video file\n- `file_path` field from the response of the [upload urls API](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls).\n\nSee the [file upload guide](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls#input-file) for details.\n" }, "end_image_file_path": { "type": "string", "minLength": 1, "description": "The image to use as the last frame of the video.\n\n* **`gemini-omni-1.1`**: Supports 360p, 720p, 1080p, 4k.\n* **`kling-2.6`**: Supports 1080p.\n* **`kling-3.0`**: Supports 720p, 1080p, 4k.\n* **`ltx-2.5`**: Supports 480p, 720p, 1080p.\n* **`minimax-h3`**: Supports 480p, 720p, 1080p.\n* **`seedance-1.5`**: Supports 480p, 720p, 1080p.\n* **`seedance-2.0`**: Supports 480p, 720p, 1080p, 4k.\n* **`seedance-2.0-fast`**: Supports 480p, 720p.\n* **`seedance-2.0-mini`**: Supports 480p, 720p.\n* **`seedance-2.5`**: Supports 480p, 720p, 1080p.\n* **`veo3.1`**: Supports 720p, 1080p. Requires a duration of 8 seconds or less.\n* **`veo3.1-lite`**: Supports 720p, 1080p. Requires a duration of 8 seconds or less.\n* **`wan-2.2`**: Not supported\n* **`wan-3.0`**: Supports 480p, 720p, 1080p.\n" } }, "required": [ "image_file_path" ], "additionalProperties": true, "description": "Provide the assets for image-to-video." }, "nullable": false, "handle": "assets" } ], "outputs": [ { "description": "Success", "jsonSchema": { "type": "object", "properties": { "id": { "type": "string", "description": "Unique ID of the video. Use it with the [Get video Project API](https://docs.magichour.ai/api-reference/video-projects/get-video-details) to fetch status and downloads." }, "credits_charged": { "type": "integer", "description": "The amount of credits deducted from your account to generate the video. If the status is not 'complete', this value is an estimate and may be adjusted upon completion based on the actual FPS of the output video. \n\nIf video generation fails, credits will be refunded, and this field will be updated to include the refund." } }, "required": [ "id", "credits_charged" ], "additionalProperties": true, "description": "Success" }, "nullable": false, "handle": "output" } ], "name": "Generate the video", "executor": { "kind": "connector", "action": "magichour.create_image_to_video", "outputMode": "value" } } }, "n_643e11db2e2f777e": { "inputs": { "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_46f4e878a2cf127f", "output": "output" } ] } }, "name": "Read the project ID", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "properties": { "id": { "type": "string", "description": "Unique ID of the video. Use it with the [Get video Project API](https://docs.magichour.ai/api-reference/video-projects/get-video-details) to fetch status and downloads." }, "credits_charged": { "type": "integer", "description": "The amount of credits deducted from your account to generate the video. If the status is not 'complete', this value is an estimate and may be adjusted upon completion based on the actual FPS of the output video. \n\nIf video generation fails, credits will be refunded, and this field will be updated to include the refund." } }, "required": [ "id", "credits_charged" ], "additionalProperties": true, "description": "Success" }, "nullable": false, "handle": "body" } ], "outputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "id" } ], "name": "Read the project ID", "moduleId": "n_6897b37454eed9d3", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_198f105b51919ca2": { "inputs": {}, "name": "Wait for the video", "kind": "wait", "inputDefinitions": [], "prompt": "Wait for the video", "autoContinue": { "kind": "after", "durationMs": 10000 } }, "n_7da1d9d477c9eb36": { "inputs": { "id": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_643e11db2e2f777e", "output": "id" } ] } }, "name": "Check the video project", "kind": "task", "task": { "inputs": [ { "description": "Unique ID of the video project. This value is returned by all of the POST APIs that create a video.", "jsonSchema": { "type": "string", "description": "Unique ID of the video project. This value is returned by all of the POST APIs that create a video." }, "nullable": false, "handle": "id" } ], "outputs": [ { "description": "Success", "jsonSchema": { "type": "object", "properties": { "id": { "type": "string", "description": "Unique ID of the video. Use it with the [Get video Project API](https://docs.magichour.ai/api-reference/video-projects/get-video-details) to fetch status and downloads." }, "name": { "type": [ "string", "null" ], "description": "The name of the video." }, "status": { "type": "string", "enum": [ "draft", "queued", "rendering", "complete", "error", "canceled" ], "description": "The status of the video.\n\n- `draft` - the project was created but has not been submitted for rendering\n- `queued` - the job is waiting for an available server\n- `rendering` - the job is being processed; the `video.started` webhook event fires when rendering begins\n- `complete` - the job finished successfully; fires `video.completed`\n- `error` - the job failed during processing; fires `video.errored`\n- `canceled` - the job was manually canceled (for example from the Magic Hour web app)\n\n**Note:** `rendering`, `complete`, and `error` have matching webhook events; `canceled` does not - a canceled job emits no webhook event, so poll this endpoint to detect cancellation." }, "type": { "type": "string", "description": "The type of the video project. Possible values are ANIMATION, AUTO_SUBTITLE, VIDEO_TO_VIDEO, FACE_SWAP, TEXT_TO_VIDEO, IMAGE_TO_VIDEO, LIP_SYNC, TALKING_PHOTO, AVATAR, VIDEO_UPSCALER, VIDEO_EDITOR, CHARACTER_REPLACE, VIDEO_COLORIZER, VIDEO_WATERMARK_REMOVER, VIDEO_COLOR_GRADER, VIDEO_TRANSLATOR, MUSIC_VIDEO, EXTEND, AUDIO_TO_VIDEO, VIDEO_EXPANDER, UGC_AD" }, "created_at": { "type": "string", "format": "date-time", "description": "Created at." }, "enabled": { "type": "boolean", "description": "Whether this resource is active. If false, it is deleted." }, "credits_charged": { "type": "integer", "description": "The amount of credits deducted from your account to generate the video. If the status is not 'complete', this value is an estimate and may be adjusted upon completion based on the actual FPS of the output video. \n\nIf video generation fails, credits will be refunded, and this field will be updated to include the refund." }, "downloads": { "type": "array", "items": { "type": "object", "properties": { "url": { "type": "string", "format": "uri", "description": "Url." }, "expires_at": { "type": "string", "format": "date-time", "description": "Expires at." } }, "required": [ "url", "expires_at" ], "additionalProperties": true, "description": "The download URL and expiration time of the project." }, "description": "Downloads." }, "error": { "type": [ "object", "null" ], "properties": { "message": { "type": "string", "description": "Details on the reason why a failure happened." }, "code": { "type": "string", "description": "An error code to indicate why a failure happened." } }, "required": [ "message", "code" ], "additionalProperties": true, "description": "The error reported while rendering the project." }, "width": { "type": "integer", "description": "The width of the final output video. A value of -1 indicates the width can be ignored." }, "height": { "type": "integer", "description": "The height of the final output video. A value of -1 indicates the height can be ignored." }, "start_seconds": { "type": "number", "minimum": 0, "description": "Start time of your clip (seconds). Must be ≥ 0." }, "end_seconds": { "type": "number", "minimum": 0.1, "description": "End time of your clip (seconds). Must be greater than start_seconds." }, "fps": { "type": "number", "description": "Frame rate of the video. If the status is not 'complete', the frame rate is an estimate and will be adjusted when the video completes." } }, "required": [ "id", "name", "status", "type", "created_at", "enabled", "credits_charged", "downloads", "error", "width", "height", "start_seconds", "end_seconds", "fps" ], "additionalProperties": true, "description": "Success" }, "nullable": false, "handle": "output" } ], "name": "Check the video project", "executor": { "kind": "connector", "action": "magichour.get_video_projects", "outputMode": "value" } } }, "n_48c9ae0f6ea38cf4": { "inputs": { "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_7da1d9d477c9eb36", "output": "output" } ] }, "projectId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_643e11db2e2f777e", "output": "id" } ] }, "previous": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_48c9ae0f6ea38cf4", "output": "state" } ] } }, "name": "Check video status", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "properties": { "id": { "type": "string", "description": "Unique ID of the video. Use it with the [Get video Project API](https://docs.magichour.ai/api-reference/video-projects/get-video-details) to fetch status and downloads." }, "name": { "type": [ "string", "null" ], "description": "The name of the video." }, "status": { "type": "string", "enum": [ "draft", "queued", "rendering", "complete", "error", "canceled" ], "description": "The status of the video.\n\n- `draft` - the project was created but has not been submitted for rendering\n- `queued` - the job is waiting for an available server\n- `rendering` - the job is being processed; the `video.started` webhook event fires when rendering begins\n- `complete` - the job finished successfully; fires `video.completed`\n- `error` - the job failed during processing; fires `video.errored`\n- `canceled` - the job was manually canceled (for example from the Magic Hour web app)\n\n**Note:** `rendering`, `complete`, and `error` have matching webhook events; `canceled` does not - a canceled job emits no webhook event, so poll this endpoint to detect cancellation." }, "type": { "type": "string", "description": "The type of the video project. Possible values are ANIMATION, AUTO_SUBTITLE, VIDEO_TO_VIDEO, FACE_SWAP, TEXT_TO_VIDEO, IMAGE_TO_VIDEO, LIP_SYNC, TALKING_PHOTO, AVATAR, VIDEO_UPSCALER, VIDEO_EDITOR, CHARACTER_REPLACE, VIDEO_COLORIZER, VIDEO_WATERMARK_REMOVER, VIDEO_COLOR_GRADER, VIDEO_TRANSLATOR, MUSIC_VIDEO, EXTEND, AUDIO_TO_VIDEO, VIDEO_EXPANDER, UGC_AD" }, "created_at": { "type": "string", "format": "date-time", "description": "Created at." }, "enabled": { "type": "boolean", "description": "Whether this resource is active. If false, it is deleted." }, "credits_charged": { "type": "integer", "description": "The amount of credits deducted from your account to generate the video. If the status is not 'complete', this value is an estimate and may be adjusted upon completion based on the actual FPS of the output video. \n\nIf video generation fails, credits will be refunded, and this field will be updated to include the refund." }, "downloads": { "type": "array", "items": { "type": "object", "properties": { "url": { "type": "string", "format": "uri", "description": "Url." }, "expires_at": { "type": "string", "format": "date-time", "description": "Expires at." } }, "required": [ "url", "expires_at" ], "additionalProperties": true, "description": "The download URL and expiration time of the project." }, "description": "Downloads." }, "error": { "type": [ "object", "null" ], "properties": { "message": { "type": "string", "description": "Details on the reason why a failure happened." }, "code": { "type": "string", "description": "An error code to indicate why a failure happened." } }, "required": [ "message", "code" ], "additionalProperties": true, "description": "The error reported while rendering the project." }, "width": { "type": "integer", "description": "The width of the final output video. A value of -1 indicates the width can be ignored." }, "height": { "type": "integer", "description": "The height of the final output video. A value of -1 indicates the height can be ignored." }, "start_seconds": { "type": "number", "minimum": 0, "description": "Start time of your clip (seconds). Must be ≥ 0." }, "end_seconds": { "type": "number", "minimum": 0.1, "description": "End time of your clip (seconds). Must be greater than start_seconds." }, "fps": { "type": "number", "description": "Frame rate of the video. If the status is not 'complete', the frame rate is an estimate and will be adjusted when the video completes." } }, "required": [ "id", "name", "status", "type", "created_at", "enabled", "credits_charged", "downloads", "error", "width", "height", "start_seconds", "end_seconds", "fps" ], "additionalProperties": true, "description": "Success" }, "nullable": false, "handle": "body" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "projectId" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previous" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "done" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" } ], "name": "Check video status", "moduleId": "n_31908d4984e3ee8e", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_ab1e2c17cb7e17a1": { "inputs": {}, "name": "Video complete?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "yes", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_48c9ae0f6ea38cf4", "output": "done" } }, "operator": "isTrue" } ] } ] } ] }, "n_7217601a2b861070": { "inputs": { "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_48c9ae0f6ea38cf4", "output": "url" } ] }, "id": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_643e11db2e2f777e", "output": "id" } ] } }, "name": "Download the video", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "id" } ], "outputs": [ { "jsonSchema": { "contentMediaType": "oomol/artifact" }, "nullable": false, "handle": "video" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "id" } ], "name": "Download the video", "moduleId": "n_5acdd038b381c466", "capabilities": [ { "kind": "http", "hosts": [ "videos.magichour.ai" ] } ] } }, "n_4a1f20b2f3c8e9b9": { "name": "Generated video", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "contentMediaType": "oomol/artifact" }, "nullable": false, "handle": "video" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "id" } ], "outputMappings": { "video": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_7217601a2b861070", "output": "video" } ] }, "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_7217601a2b861070", "output": "url" } ] }, "id": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_7217601a2b861070", "output": "id" } ] } } } }, "edges": [ { "source": "n_5d9e83cbd59f1c20", "target": "n_46f4e878a2cf127f" }, { "source": "n_46f4e878a2cf127f", "target": "n_643e11db2e2f777e" }, { "source": "n_643e11db2e2f777e", "target": "n_198f105b51919ca2" }, { "source": "n_198f105b51919ca2", "target": "n_7da1d9d477c9eb36", "sourceHandle": "continue" }, { "source": "n_7da1d9d477c9eb36", "target": "n_48c9ae0f6ea38cf4" }, { "source": "n_48c9ae0f6ea38cf4", "target": "n_ab1e2c17cb7e17a1" }, { "source": "n_ab1e2c17cb7e17a1", "target": "n_7217601a2b861070", "sourceHandle": "yes" }, { "source": "n_ab1e2c17cb7e17a1", "target": "n_198f105b51919ca2", "sourceHandle": "otherwise" }, { "source": "n_7217601a2b861070", "target": "n_4a1f20b2f3c8e9b9" } ] } }, "modules": { "n_6897b37454eed9d3": { "name": "Read the project ID", "imports": [], "source": "export default function created({body}){if(!body||typeof body.id!=='string'||!body.id)throw Error('Missing project ID');return{id:body.id}}" }, "n_31908d4984e3ee8e": { "name": "Check video status", "imports": [], "source": "export default function status({body,projectId,previous}){const {id,status,downloads}=body??{};if(id!==projectId)throw Error('Project ID mismatch');const count=previous==null?1:previous.count+1;if(!Number.isInteger(count)||count>60)throw Error('Polling timed out for project '+projectId);if(status==='error'||status==='canceled')throw Error('Generation '+status+' for project '+projectId);if(status==='complete'){if(!Array.isArray(downloads)||!downloads[0]||typeof downloads[0].url!=='string')throw Error('Missing download for project '+projectId);return{state:{count},done:true,url:downloads[0].url}}if(!['draft','queued','rendering'].includes(status))throw Error('Unexpected status for project '+projectId);if(count===60)throw Error('Polling timed out for project '+projectId);return{state:{count},done:false,url:''}}\n" }, "n_5acdd038b381c466": { "name": "Download the video", "imports": [], "source": "import{Buffer}from'node:buffer';\nexport default async function download({url,id},context){if(typeof url!=='string'||!/^https:\\/\\//.test(url))throw Error('Invalid video URL for project '+id);const r=await context.fetch(url,{method:'GET'});if(!r.ok)throw Error('Download failed for project '+id);const b=Buffer.from(await r.arrayBuffer());if(b.length<12||b.subarray(4,8).toString()!=='ftyp')throw Error('Invalid MP4 for project '+id);const video=await context.artifact.put(b,{name:'video.mp4',mediaType:'video/mp4'});return{video,url,id}}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }