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:
CODEBLOCK0
- - 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):
CODEBLOCK1
Telegram / Discord / Other channels — send plain text via message tool (these channels support markdown natively):
CODEBLOCK2
Once they provide the key: INLINECODE5
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)
- Once they provide the key: INLINECODE7
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
- 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
CODEBLOCK3
Usage 2: Text + uploaded image (user sends image in chat)
CODEBLOCK4
Usage 3: Text + image URL
CODEBLOCK5
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 |
|---|
| INLINECODE9 | When a specific voice is needed | Medeo picks automatically |
| INLINECODE10 |
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 | INLINECODE19 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) | INLINECODE27 |
| 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: <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)
CODEBLOCK6
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.
CODEBLOCK7
Output: INLINECODE49
Then pass media_id to generation:
CODEBLOCK8
Platform-Specific Image Extraction Guide
| Platform | How to get image source | INLINECODE51 arg |
|---|
| Telegram | INLINECODE52 | INLINECODE53 |
| 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
CODEBLOCK9
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:
CODEBLOCK10
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.
CODEBLOCK11
| Command | What it does | Key args |
|---|
| INLINECODE76 | Blocking full pipeline (upload→compose→render) | Same as spawn-task minus deliver flags |
| INLINECODE77 |
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
CODEBLOCK12
Use in generation: --recipe-id "recipe_01...". See docs/recipes.md.
6. Quick Commands Reference (for agent, not user-facing)
| Command | Description | User-visible? |
|---|
| INLINECODE91 | List video templates | Yes — "what templates are available?" |
| INLINECODE92 |
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 - One call for stories — full storylines in one
--message, never split - 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
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. - 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 "<thumbnail_url>" --duration <ms> (use
oc_ chat
id for group chats, ou_ openid 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 "<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
message tool directly — download the video to
/tmp/result.mp4 via
curl -sL -o /tmp/result.mp4 "<video_url>", then call
message(action="send", channel="discord", target="<channel_id>", 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 |
|---|
| INLINECODE132 | Guide user to register + configure key |
| INLINECODE133 |
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
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.
Medeo 视频生成技能
从文本生成AI视频。Medeo是一个AI视频代理,可在单次调用中处理完整故事情节、多场景叙事和剧本——镜头构图、转场、节奏和音乐均为自动处理。
⚠️ 请勿将故事拆分为多次调用。 请通过一个--message传递完整剧本。
0. 预检检查(强制——在任何操作前运行)
在运行任何命令之前,检查API密钥是否已配置:
bash
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)
- - 输出ok → 正常继续
- 输出missing(或命令失败)→ 立即停止,不要运行任何其他命令。使用渠道适切的方法发送设置消息:
飞书 — 直接使用飞书API(不要使用message工具——它无法渲染卡片):
python
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/tenantaccesstoken/internal,
data=json.dumps({appid: feishu[appId], appsecret: feishu[appSecret]}).encode(),
headers={Content-Type: application/json}
)).read())[tenantaccesstoken]
card = {
config: {widescreenmode: True},
header: {title: {tag: plain_text, content: 🎬 视频生成 — 需要API密钥}, template: blue},
elements: [{tag: div, text: {tag: larkmd, content: 你需要一个Medeo API密钥来生成视频。\n\n步骤:\n1. 前往 https://medeo.app/dev/apikey\n - 没有账号?系统会引导你注册。登录后密钥会显示。\n2. 复制密钥(以mk开头)并发送给我。\n\n收到密钥后,我会为你完成所有配置。}}],
}
urllib.request.urlopen(urllib.request.Request(
https://open.feishu.cn/open-apis/im/v1/messages?receiveidtype=open_id,
data=json.dumps({receiveid: OPENID>, msgtype: interactive, content: json.dumps(card)}).encode(),
headers={Authorization: fBearer {token}, Content-Type: application/json}
))
Telegram / Discord / 其他渠道 — 通过message工具发送纯文本(这些渠道原生支持Markdown):
🎬 视频生成 — 需要API密钥
步骤:
- 1. 前往 https://medeo.app/dev/apikey(如需注册——登录后密钥会显示)
- 复制密钥(以mk_开头)并发送给我
收到密钥后,我会为你完成所有配置。
用户提供密钥后:python3 {baseDir}/scripts/medeovideo.py config-init --api-key mk...
1. 首次设置
如果未配置API密钥,脚本会输出setup_required: true。
- 1. 向用户发送此确切链接:https://medeo.app/dev/apikey(如果未登录,此页面会自动提示注册,然后显示API密钥)
- 用户提供密钥后:python3 {baseDir}/scripts/medeovideo.py config-init --api-key mk...
2. 生成视频(5-30分钟,始终异步)
用户只需知道3种方式来生成视频:
- 1. 发送文本 → 生成视频
- 发送文本 + 上传图片 → 使用他们的图片生成视频
- 发送文本 + 图片URL → 使用URL中的图片生成视频
代理会静默处理其他所有事项。
重要:在启动生成任务之前,立即回复用户,发送确认消息,例如:
🎬 正在开始视频生成——我将在5-10分钟内将结果发送给你。
不要静默等待。用户应该知道他们的请求已被接收。
用法1:仅文本
bash
python3 {baseDir}/scripts/medeo_video.py spawn-task \
--message 用户的视频描述 \
--deliver-to oc_xxx \
--deliver-channel feishu
用法2:文本 + 上传的图片(用户在聊天中发送图片)
bash
首先:upload-file获取media_id(参见第3节)
python3 {baseDir}/scripts/medeo_video.py spawn-task \
--message 用户的视频描述 \
--media-ids media_01... \
--asset-sources my
uploadedassets \
--deliver-to oc_xxx \
--deliver-channel feishu
用法3:文本 + 图片URL
bash
python3 {baseDir}/scripts/medeo_video.py spawn-task \
--message 用户的视频描述 \
--media-urls https://example.com/photo.jpg \
--asset-sources myuploadedassets \
--deliver-to oc_xxx \
--deliver-channel feishu
代理自动行为: 当用户提供图片时(用法2或3),始终传递--asset-sources myuploadedassets,以便Medeo使用他们的图片而不是生成新的。用户无需知道此标志的存在。
内部参数(仅代理使用——切勿向用户暴露)
这些由代理自动处理。不要向用户提及或要求用户提供。
| 标志 | 何时使用 | 默认行为 |
|---|
| --voice-id voice01... | 需要特定语音时 | Medeo自动选择 |
| --video-style-id style01... |
需要特定视觉风格时 | Medeo自动选择 |
| --asset-sources | 用户提供图片时:传递my
uploadedassets | Medeo决定 |
| --recipe-id recipe_01... | 使用模板时 | 无 |
| --aspect-ratio 9:16 | 用户指定竖屏/横屏时 | 16:9 |
| --duration-ms 30000 | 用户指定时长时 | Medeo决定 |
| --no-render | 仅调试——跳过渲染 | 始终渲染 |
交付目标(--deliver-to)
这很关键——决定生成的视频发送到哪里。
| 上下文 | --deliver-to 值 | 示例 |
|---|
| 飞书群聊 | 群的chatid(以oc开头)。从入站元数据conversationlabel或chatid中提取——如果存在,去掉chat:前缀(例如chat:ocxxx → ocxxx) | oc158fd3e54407cbe170697c6c954bd4f2 |
| 飞书私聊 |
用户的openid(以ou
开头)。从入站元数据senderid中提取——
如果存在,去掉user:前缀 | ou_f7f458f4d7b4ff49ec1b8de22a1e3206 |
|
Telegram | 入站消息上下文中的chat_id | -1001234567890 |
|
Discord | 入站消息上下文中的channel_id | 1234567890123456 |
如何判断飞书上的群聊与私聊:
- - 检查入站元数据中的isgroupchat
- 如果为true → 使用conversationlabel / chatid(oc值)
- 如果为false → 使用senderid(ou_值)
步骤2:使用返回的参数调用sessions_spawn(label: medeo: <简要描述>,runTimeoutSeconds: 2400)。
步骤3:告知用户正在生成。子代理完成后会自动通知。
3. 上传素材
3a. 从URL上传(图片已有公开URL)
bash
python3 {baseDir}/scripts/medeo_video.py upload \
--url https://example.com/photo.jpg \
--project-id project_01... # 可选:将媒体关联到现有项目
--no-wait # 可选:立即返回job_id,不轮询
###