{ "kind": "open-flow-workflow", "version": 1, "name": "Recover a Magic Hour Video", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_41f8653a711f30ea": { "name": "Start manually", "kind": "start", "inputDefs": [] }, "n_2e5b460991ef1ae7": { "inputs": { "id": { "kind": "value", "value": "YOUR_VIDEO_PROJECT_ID" } }, "name": "Find 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": "Find the video project", "executor": { "kind": "connector", "action": "magichour.get_video_projects", "outputMode": "value" } } }, "n_5ae59fd4ee634ec3": { "inputs": { "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_2e5b460991ef1ae7", "output": "output" } ] }, "projectId": { "kind": "value", "value": "YOUR_VIDEO_PROJECT_ID" } }, "name": "Require a completed video", "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" } ], "outputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "id" } ], "name": "Require a completed video", "moduleId": "n_66e32a1c9f3df85e", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_6e60da95a20d1747": { "inputs": { "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5ae59fd4ee634ec3", "output": "url" } ] }, "id": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5ae59fd4ee634ec3", "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_427e739a4e068356", "capabilities": [ { "kind": "http", "hosts": [ "videos.magichour.ai" ] } ] } }, "n_675a3dcaa72eb699": { "name": "Recovered 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_6e60da95a20d1747", "output": "video" } ] }, "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_6e60da95a20d1747", "output": "url" } ] }, "id": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_6e60da95a20d1747", "output": "id" } ] } } } }, "edges": [ { "source": "n_41f8653a711f30ea", "target": "n_2e5b460991ef1ae7" }, { "source": "n_2e5b460991ef1ae7", "target": "n_5ae59fd4ee634ec3" }, { "source": "n_5ae59fd4ee634ec3", "target": "n_6e60da95a20d1747" }, { "source": "n_6e60da95a20d1747", "target": "n_675a3dcaa72eb699" } ] } }, "modules": { "n_66e32a1c9f3df85e": { "name": "Require a completed video", "imports": [], "source": "export default function recover({body,projectId}){if(!body||body.id!==projectId)throw Error('Project ID mismatch');if(body.status!=='complete')throw Error('Video is '+body.status+' for project '+projectId);if(!Array.isArray(body.downloads)||!body.downloads[0]||typeof body.downloads[0].url!=='string')throw Error('Missing video download for project '+projectId);return{url:body.downloads[0].url,id:projectId}}\n" }, "n_427e739a4e068356": { "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" } } } }