🎁 Get the FREE AI Skills Starter Guide β€” Subscribe β†’
BytesAgainBytesAgain
πŸ¦€ ClawHub

Medeo Video

by @sunnykui

AI-powered video generation skill. Use when the user wants to generate videos from text descriptions, browse video recipes, upload assets, or manage video cr...

TERMINAL
clawhub install medeo-video

πŸ“– About This Skill


name: medeo-video version: 1.5.2 description: AI-powered video generation skill. Use when the user wants to generate videos from text descriptions, browse video recipes, upload assets, or manage video creation workflows. metadata: {"openclaw":{"emoji":"🎬","requires":{"bins":["python3"],"env":{"MEDEO_API_KEY":{"required":true,"description":"Medeo API key (starts with mk_)"}}},"tags":["video","ai-video","medeo","video-generation","rendering","recipes","media","upload"]}}

Medeo Video Generator Skill

Generate AI videos from text. Medeo is an AI video agent that handles full storylines, multi-scene narratives, and screenplays in a single call β€” shot composition, transitions, pacing, and music are all automatic.

> ⚠️ Do NOT split stories into multiple calls. Pass the entire screenplay in one --message.

0. Pre-Flight Check (MANDATORY β€” run before anything else)

Before running any command, check if API key is configured:

python3 {baseDir}/scripts/medeo_video.py config 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print('ok' if d.get('apiKey') else 'missing')"

  • Output ok β†’ proceed normally
  • Output missing (or command fails) β†’ stop immediately, do NOT run any other commands. Send the setup message using the channel-appropriate method:
  • Feishu β€” use Feishu API directly (NOT message tool β€” it won't render cards):

    import json, urllib.request
    cfg = json.loads(open("/home/ec2-user/.openclaw/openclaw.json").read())
    feishu = cfg["channels"]["feishu"]["accounts"]["default"]
    token = json.loads(urllib.request.urlopen(urllib.request.Request(
        "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
        data=json.dumps({"app_id": feishu["appId"], "app_secret": feishu["appSecret"]}).encode(),
        headers={"Content-Type": "application/json"}
    )).read())["tenant_access_token"]
    card = {
        "config": {"wide_screen_mode": True},
        "header": {"title": {"tag": "plain_text", "content": "🎬 Video Generation β€” API Key Required"}, "template": "blue"},
        "elements": [{"tag": "div", "text": {"tag": "lark_md", "content": "You need a Medeo API Key to generate videos.\n\nSteps:\n1. Go to https://medeo.app/dev/apikey\n   - No account? You'll be guided to sign up. The key appears after login.\n2. Copy the key (starts with mk_) and send it back to me.\n\nOnce I have it, I'll configure everything for you."}}],
    }
    urllib.request.urlopen(urllib.request.Request(
        "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id",
        data=json.dumps({"receive_id": "", "msg_type": "interactive", "content": json.dumps(card)}).encode(),
        headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
    ))
    

    Telegram / Discord / Other channels β€” send plain text via message tool (these channels support markdown natively):

    🎬 Video Generation β€” API Key Required

    Steps: 1. Go to https://medeo.app/dev/apikey (sign up if needed β€” the key appears after login) 2. Copy the key (starts with mk_) and send it back to me

    Once I have it, I'll configure everything for you.

    Once they provide the key: python3 {baseDir}/scripts/medeo_video.py config-init --api-key "mk_..."

    1. First-Time Setup

    If no API Key is configured, the script outputs "setup_required": true. 1. Send the user this exact link: https://medeo.app/dev/apikey (this page auto-prompts registration if not logged in, then shows the API key) 2. Once they provide the key: python3 {baseDir}/scripts/medeo_video.py config-init --api-key "mk_..."

    2. Generate a Video (5-30 min, always async)

    Users only need to know 3 ways to generate a video:

    1. Send text β†’ generate video 2. Send text + upload image β†’ generate video using their image 3. Send text + image URL β†’ generate video using the URL image

    The agent handles everything else silently.

    > IMPORTANT: Before spawning the generation task, immediately reply to the user with an acknowledgment like: > "🎬 Starting video generation β€” I'll send you the result in about 5–10 minutes." > Do NOT wait in silence. The user should know their request was received.

    Usage 1: Text only

    python3 {baseDir}/scripts/medeo_video.py spawn-task \
      --message "user's video description" \
      --deliver-to "oc_xxx" \
      --deliver-channel "feishu"
    

    Usage 2: Text + uploaded image (user sends image in chat)

    # First: upload-file to get media_id (see Section 3)
    python3 {baseDir}/scripts/medeo_video.py spawn-task \
      --message "user's video description" \
      --media-ids "media_01..." \
      --asset-sources my_uploaded_assets \
      --deliver-to "oc_xxx" \
      --deliver-channel "feishu"
    

    Usage 3: Text + image URL

    python3 {baseDir}/scripts/medeo_video.py spawn-task \
      --message "user's video description" \
      --media-urls "https://example.com/photo.jpg" \
      --asset-sources my_uploaded_assets \
      --deliver-to "oc_xxx" \
      --deliver-channel "feishu"
    

    > Agent auto-behavior: When the user provides images (Usage 2 or 3), always pass --asset-sources my_uploaded_assets so Medeo uses their images instead of generating new ones. The user does not need to know this flag exists.

    Internal Parameters (agent use only β€” never expose to users)

    These are handled automatically by the agent. Do NOT mention them to users or ask users to provide them.

    | Flag | When to use | Default behavior | |------|------------|-----------------| | --voice-id "voice_01..." | When a specific voice is needed | Medeo picks automatically | | --video-style-id "style_01..." | When a specific visual style is needed | Medeo picks automatically | | --asset-sources | When user provides images: pass my_uploaded_assets | Medeo decides | | --recipe-id "recipe_01..." | When using a template | None | | --aspect-ratio "9:16" | When user specifies portrait/landscape | 16:9 | | --duration-ms 30000 | When user specifies duration | Medeo decides | | --no-render | Debug only β€” skip rendering | Always render |

    Delivery Target (--deliver-to)

    This is critical β€” determines where the generated video gets sent.

    | Context | --deliver-to value | Example | |---------|---------------------|---------| | Feishu group chat | The group's chat_id (starts with oc_). Extract from inbound metadata conversation_label or chat_id β€” strip the chat: prefix if present (e.g. chat:oc_xxx β†’ oc_xxx) | oc_158fd3e54407cbe170697c6c954bd4f2 | | Feishu private chat | The user's open_id (starts with ou_). Extract from inbound metadata sender_id β€” strip the user: prefix if present | ou_f7f458f4d7b4ff49ec1b8de22a1e3206 | | Telegram | The chat_id from the inbound message context | -1001234567890 | | Discord | The channel_id from the inbound message context | 1234567890123456 |

    How to determine group vs private on Feishu:

  • Check is_group_chat in the inbound metadata
  • If true β†’ use conversation_label / chat_id (the oc_ value)
  • If false β†’ use sender_id (the ou_ value)
  • Step 2: Use sessions_spawn with the returned args (label: "medeo: ", runTimeoutSeconds: 2400). Step 3: Tell user it's generating. Sub-agent auto-announces when done.

    3. Upload Assets

    3a. From URL (image already has a public URL)

    python3 {baseDir}/scripts/medeo_video.py upload \
      --url "https://example.com/photo.jpg" \
      --project-id "project_01..."          # optional: associate media with existing project
      --no-wait                             # optional: return job_id immediately without polling
    

    3b. From IM attachment (user sends image directly) ← NEW

    Use upload-file when the user sends an image via Telegram, Discord, Feishu, or as a local file. This uses the direct upload API (prepare β†’ S3 presigned PUT β†’ register) instead of URL-based upload.

    Trigger: Only when the user explicitly requests video generation AND sends an image attachment in the same message (e.g. "make a video with this photo"). Do NOT auto-upload on every image message β€” other skills or conversations may involve images unrelated to video generation.

    # From local file (downloaded by OpenClaw from attachment)
    python3 {baseDir}/scripts/medeo_video.py upload-file \
      --file /tmp/user_photo.jpg

    From direct URL (Discord CDN, etc.)

    python3 {baseDir}/scripts/medeo_video.py upload-file \ --url "https://cdn.discordapp.com/attachments/..."

    From Telegram (file_id from message.photo[-1].file_id)

    TELEGRAM_BOT_TOKEN must be set as env var β€” never pass as CLI arg (ps aux leaks it)

    TELEGRAM_BOT_TOKEN="$TELEGRAM_BOT_TOKEN" python3 {baseDir}/scripts/medeo_video.py upload-file \ --telegram-file-id "AgACAgIAAxk..."

    From Feishu (message_id + image_key from message content)

    python3 {baseDir}/scripts/medeo_video.py upload-file \ --feishu-message-id "om_xxx" \ --feishu-image-key "img_v3_xxx" \ --feishu-app-token "$FEISHU_APP_TOKEN"

    Output: {"media_id": "media_01...", "filename": "photo.jpg"}

    Then pass media_id to generation:

    python3 {baseDir}/scripts/medeo_video.py spawn-task \
      --message "Create a video featuring this person" \
      --media-ids "media_01..."
    

    Platform-Specific Image Extraction Guide

    | Platform | How to get image source | upload-file arg | |----------|------------------------|-------------------| | Telegram | message.photo[-1].file_id | --telegram-file-id | | Discord | message.attachments[0].url (public CDN URL) | --url | | Feishu | message_id + image_key from message content JSON | --feishu-message-id + --feishu-image-key | | WhatsApp | Download attachment binary β†’ save to /tmp | --file | | Generic URL | Any direct image URL | --url |

    Note: Discord attachment URLs are public CDN links β€” --url works directly. All other platforms require authentication to download.

    3c. Inline in generate pipeline

    # URL-based (existing behavior)
    python3 {baseDir}/scripts/medeo_video.py spawn-task \
      --message "Product showcase for this sneaker" \
      --media-urls "https://example.com/front.jpg" "https://example.com/side.jpg"
    

    Supports .jpg, .png, .webp, .mp4, .mov, .gif. Higher resolution + multiple angles = better results.

    3d. Check Upload Status

    After upload or upload-file, if you need to check the upload job:

    python3 {baseDir}/scripts/medeo_video.py upload-status --job-id "job_01..."
    

    Returns media status (processing, completed, failed) and media_id once done.

    4. Low-Level Pipeline Commands (agent internal β€” never expose to users)

    > These are for agent debugging or manual intervention only. Users should never see these commands.

    Pipeline flow:
      spawn-task (recommended, async)
          └── generate (blocking, same pipeline)
                  β”œβ”€β”€ upload (if --media-urls)
                  β”œβ”€β”€ compose β†’ compose-status (poll)
                  └── render β†’ render-status (poll)
    

    | Command | What it does | Key args | |---------|-------------|----------| | generate | Blocking full pipeline (upload→compose→render) | Same as spawn-task minus deliver flags | | compose | Create project only (no render) | --message, --media-ids, --recipe-id | | compose-status | Poll compose task | --task-id "task_01..." | | render | Render existing project | --project-id "project_01..." | | render-status | Poll render job | --job-id "render_01..." | | upload-status | Poll upload job | --job-id "job_01..." |

    All commands support --no-wait to return immediately without polling.

    5. Browse Recipes

    python3 {baseDir}/scripts/medeo_video.py recipes              # list templates
    python3 {baseDir}/scripts/medeo_video.py recipes --cursor   # paginate
    

    Use in generation: --recipe-id "recipe_01...". See docs/recipes.md.

    6. Quick Commands Reference (for agent, not user-facing)

    | Command | Description | User-visible? | |---------|-------------|--------------| | recipes | List video templates | Yes β€” "what templates are available?" | | last-job | Latest job status | Yes β€” "is my last video done?" | | history | Job history (last 50) | Yes β€” "show my video history" | | config | Show current configuration | No | | config-init --api-key "mk_..." | Initialize API key | Only during setup | | upload --url "URL" | Upload from public URL | No (agent internal) | | upload-file --file PATH | Upload from local file | No (agent internal) | | upload-file --url "URL" | Download URL β†’ upload | No (agent internal) | | upload-file --telegram-file-id "..." | Upload Telegram attachment | No (agent internal) | | upload-file --feishu-image-key "..." | Upload Feishu attachment | No (agent internal) | | upload-status --job-id "..." | Check upload job status | No (agent internal) | | compose-status --task-id "..." | Check compose task progress | No (agent internal) | | render-status --job-id "..." | Check render job progress | No (agent internal) |

    7. Key Rules

    1. Always async β€” spawn-task + sessions_spawn for generation 2. One call for stories β€” full storylines in one --message, never split 3. Insufficient credits β€” share recharge link from error output 4. IM image upload β€” Only upload images when the user explicitly asks for video generation with that image. Do NOT auto-upload every image message (user may have other skills installed). When triggered: run upload-file first β†’ get media_id β†’ pass to generation via --media-ids. Never ask the user for a URL if they already sent the image. 5. IM-native delivery β€” After generation, deliver the video using the IM channel's native method (not just a URL). Each channel has a dedicated delivery script: - Feishu: python3 {baseDir}/scripts/feishu_send_video.py --video /tmp/result.mp4 --to "oc_xxx_or_ou_xxx" --cover-url "" --duration (use oc_ chat_id for group chats, ou_ open_id for private chats; chat:oc_xxx and user:ou_xxx prefixed forms are also accepted) - Telegram: Download video, then send via telegram_send_video.py (token from env only):

         curl -sL -o /tmp/medeo_result.mp4 ""
         TELEGRAM_BOT_TOKEN="$TELEGRAM_BOT_TOKEN" python3 {baseDir}/scripts/telegram_send_video.py \
           --video /tmp/medeo_result.mp4 \
           --to "" \
           --cover-url "" \
           --duration  \
           --caption "🎬 Video ready!"
         
    - Discord: Use the message tool directly β€” download the video to /tmp/result.mp4 via curl -sL -o /tmp/result.mp4 "", then call message(action="send", channel="discord", target="", message="🎬 Video ready!", filePath="/tmp/result.mp4"). For files >25 MB, send video_url as a plain link instead. - WhatsApp / Signal / Other: Use the message tool with media parameter, or share video_url as a link if native sending is unavailable. - Cover image URL: The generate output JSON includes thumbnail_url β€” the API always returns this field. Constructed as {ossBaseUrl}/{thumbnail_relative_path} (e.g. https://oss.prd.medeo.app/assets/medias/media_xxx.png). - Video URL: Same pattern β€” {ossBaseUrl}/{video_relative_path} (e.g. https://oss.prd.medeo.app/exported_video/v_xxx). - Security: Never pass bot tokens as CLI args (visible in ps). Always use env vars: TELEGRAM_BOT_TOKEN, DISCORD_BOT_TOKEN. 6. Timeline completion β€” Medeo's backend is an AI agent. Generated images/videos must be added to the Timeline to trigger task completion and rendering. Always append to your prompt: "Add the generated video/image to the Timeline."

    8. Error Handling

    | Error | Action | |-------|--------| | setup_required: true | Guide user to register + configure key | | upload_prep_rejected | File format/size rejected; check supported formats | | s3_put_failed | S3 upload error; retry once | | Insufficient credits | Share recharge link from error output, retry after top-up | | Compose/render timeout | Inform user, suggest retry. Complex scripts may take 15+ min | | 401/403 | Key may be invalid or expired, ask user to regenerate | | Upload 404 | Some image hosts block server-side fetch; use upload-file --url to download first |

    9. Reference Docs

  • docs/recipes.md β€” Full recipe browsing and pagination
  • docs/assets-upload.md β€” All upload paths (URL, local file, IM attachments), platform-specific guides, upload vs upload-file comparison
  • docs/feishu-send.md β€” Sending generated video via Feishu (cover image, duration, compression)
  • docs/multi-platform.md β€” Multi-platform video delivery (Feishu, Telegram, Discord, WhatsApp)
  • 10. Data Storage

    All data in ~/.openclaw/workspace/medeo-video/: config.json (API key), last_job.json (latest job), history/ (last 50 jobs).

    11. Security Notes

  • API key resolution: env var MEDEO_API_KEY β†’ config.json β†’ built-in defaults. No legacy system-level files are read.
  • Feishu delivery: feishu_send_video.py reads appId + appSecret from local ~/.openclaw/openclaw.json to call Feishu Open API. Credentials stay local and are never transmitted beyond the Feishu API.
  • Telegram delivery: Bot token is read from TELEGRAM_BOT_TOKEN env var only (never CLI args).
  • No secrets in skill directory: config.json lives in the runtime data directory (~/.openclaw/workspace/medeo-video/), not in the skill source directory.