medeo-video
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.
git clone https://github.com/one2x-ai/medeo-video-skill
git clone --depth=1 https://github.com/one2x-ai/medeo-video-skill ~/.claude/skills/one2x-ai-medeo-video-skill-medeo-video
SKILL.mdMedeo 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
→ proceed normallyok - Output
(or command fails) → stop immediately, do NOT run any other commands. Send the setup message using the channel-appropriate method:missing
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\n**Steps:**\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": "<USER_OPEN_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.
- 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)
- 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:
- Send text → generate video
- Send text + upload image → generate video using their image
- 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
so Medeo uses their images instead of generating new ones. The user does not need to know this flag exists.--asset-sources my_uploaded_assets
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 |
|---|---|---|
| When a specific voice is needed | Medeo picks automatically |
| When a specific visual style is needed | Medeo picks automatically |
| When user provides images: pass | Medeo decides |
| When using a template | None |
| When user specifies portrait/landscape | |
| When user specifies duration | Medeo decides |
| Debug only — skip rendering | Always render |
Delivery Target (--deliver-to
)
--deliver-toThis is critical — determines where the generated video gets sent.
| Context | value | Example |
|---|---|---|
| Feishu group chat | The group's (starts with ). Extract from inbound metadata or — strip the prefix if present (e.g. → ) | |
| Feishu private chat | The user's (starts with ). Extract from inbound metadata — strip the prefix if present | |
| Telegram | The from the inbound message context | |
| Discord | The from the inbound message context | |
How to determine group vs private on Feishu:
- Check
in the inbound metadatais_group_chat - If
→ usetrue
/conversation_label
(thechat_id
value)oc_ - If
→ usefalse
(thesender_id
value)ou_
Step 2: Use
sessions_spawn with the returned args (label: "medeo: <brief>", 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 | arg |
|---|---|---|
| Telegram | | |
| Discord | (public CDN URL) | |
| Feishu | + from message content JSON | + |
Download attachment binary → save to | | |
| Generic URL | Any direct image 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 |
|---|---|---|
| Blocking full pipeline (upload→compose→render) | Same as spawn-task minus deliver flags |
| Create project only (no render) | , , |
| Poll compose task | |
| Render existing project | |
| Poll render job | |
| Poll upload job | |
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 <c> # 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? |
|---|---|---|
| List video templates | Yes — "what templates are available?" |
| Latest job status | Yes — "is my last video done?" |
| Job history (last 50) | Yes — "show my video history" |
| Show current configuration | No |
| Initialize API key | Only during setup |
| Upload from public URL | No (agent internal) |
| Upload from local file | No (agent internal) |
| Download URL → upload | No (agent internal) |
| Upload Telegram attachment | No (agent internal) |
| Upload Feishu attachment | No (agent internal) |
| Check upload job status | No (agent internal) |
| Check compose task progress | No (agent internal) |
| Check render job progress | No (agent internal) |
7. Key Rules
- Always async —
+spawn-task
for generationsessions_spawn - One call for stories — full storylines in one
, never split--message - Insufficient credits — share recharge link from error output
- 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
first → getupload-file
→ pass to generation viamedia_id
. Never ask the user for a URL if they already sent the image.--media-ids - 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:
(usepython3 {baseDir}/scripts/feishu_send_video.py --video /tmp/result.mp4 --to "oc_xxx_or_ou_xxx" --cover-url "<thumbnail_url>" --duration <ms>
chat_id for group chats,oc_
open_id for private chats;ou_
andchat:oc_xxx
prefixed forms are also accepted)user:ou_xxx - Telegram: Download video, then send via
(token from env only):telegram_send_video.pycurl -sL -o /tmp/medeo_result.mp4 "<video_url>" TELEGRAM_BOT_TOKEN="$TELEGRAM_BOT_TOKEN" python3 {baseDir}/scripts/telegram_send_video.py \ --video /tmp/medeo_result.mp4 \ --to "<chat_id>" \ --cover-url "<thumbnail_url>" \ --duration <seconds> \ --caption "🎬 Video ready!" - Discord: Use the
tool directly — download the video tomessage
via/tmp/result.mp4
, then callcurl -sL -o /tmp/result.mp4 "<video_url>"
. For files >25 MB, sendmessage(action="send", channel="discord", target="<channel_id>", message="🎬 Video ready!", filePath="/tmp/result.mp4")
as a plain link instead.video_url - WhatsApp / Signal / Other: Use the
tool withmessage
parameter, or sharemedia
as a link if native sending is unavailable.video_url - Cover image URL: The generate output JSON includes
— the API always returns this field. Constructed asthumbnail_url
(e.g.{ossBaseUrl}/{thumbnail_relative_path}
).https://oss.prd.medeo.app/assets/medias/media_xxx.png - Video URL: Same pattern —
(e.g.{ossBaseUrl}/{video_relative_path}
).https://oss.prd.medeo.app/exported_video/v_xxx - Security: Never pass bot tokens as CLI args (visible in
). Always use env vars:ps
,TELEGRAM_BOT_TOKEN
.DISCORD_BOT_TOKEN
- Feishu:
- 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 |
|---|---|
| Guide user to register + configure key |
| File format/size rejected; check supported formats |
| 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 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,
vsupload
comparisonupload-file - 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
→ built-in defaults. No legacy system-level files are read.config.json - Feishu delivery:
readsfeishu_send_video.py
+appId
from localappSecret
to call Feishu Open API. Credentials stay local and are never transmitted beyond the Feishu API.~/.openclaw/openclaw.json - Telegram delivery: Bot token is read from
env var only (never CLI args).TELEGRAM_BOT_TOKEN - No secrets in skill directory:
lives in the runtime data directory (config.json
), not in the skill source directory.~/.openclaw/workspace/medeo-video/