pandastudio
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese—
核心编辑工作流——按平台定制的完整方案
<!-- version: 3.104.0 -->
当用户说“编辑这个”/“打磨这个”/“让它适配<X>平台”/“做好YouTube发布准备”时,请遵循本手册。它会借助上述基础操作,将原始录制内容转化为经过打磨、适配目标平台的视频。不同平台(YouTube长视频、Shorts/TikTok、LinkedIn、Loom)需要不同的默认设置——下表是权威参考。
PandaStudio
设计理念
🛑 Pick your interface FIRST — prefer the CLI
PandaStudio exposes the same editing surface through two transports:
CLI (localhost HTTP). Prefer this. A single bash tool covers the entire ~150-verb surface — no per-tool schema needs to live in your context. Probe once withpandastudio(or just callcommand -v pandastudio). If it succeeds, you're on the CLI path; every example in this skill is written for it directly.pandastudio system.status --json MCP server — tools prefixed(in-app PandaStudio agent) ormcp__pandastudio__*(external hosts like Cursor, Claude Desktop). Use only when the CLI is not installed — i.e.mcp__writepanda__*returned empty AND one of the MCP prefixes is visible in your tools.command -v pandastudioWhy CLI is preferred: every MCP tool definition costs context tokens (per-tool input schema + description). With ~150 verbs that overhead adds up quickly. The CLI is one bash tool — schema cost stays constant regardless of which verb you call.The verbs, argument names, and behaviors are identical across both transports. A CLI call likemaps 1:1 to the MCP toolpandastudio project.add-zoom --id=… --atMs=…with the same args (project_add_zoom). If you're on MCP fallback, translate the CLI examples below by lowercasing the verb and replacing the dot with an underscore.{id, atMs, …}Do not search the filesystem for the CLI. Don't run,ls /Applications, etc.npm list @writepanda/cliis the only probe you need; if it returns nothing, immediately move to the MCP fallback without further discovery.command -v pandastudio
Version check. This skill requires≥ 1.15.0 (or@writepanda/cli≥ 1.15.0). On the MCP path, call@writepanda/mcpand read the returned version. On the CLI path, runsystem_status. If < 1.15.0, tell the user to update (pandastudio --version) and restart their agent host. Commands likenpx @writepanda/mcp@latest,asset.list-music, andasset.list-lutsdo not exist in older versions.project.set-clip-lut
Motion graphics: use the bundled templates first
PandaStudio ships a curated set of YouTube-creator motion-graphic templates — title cards, lower thirds, stat reveals, checklists, comparisons, a host+panel split, animated-title overlays. They are production-grade and the primary path when you are layering graphics OVER existing footage (see the Mode A / Mode B note below):
→ see every template, its editable slots, and whether it's an overlay (sits over the video with alpha). It also returnsmotion_list— ~38 curated standalone compositions (effects, overlays, flowcharts, code snippet, beat-driven cuts) that have no slots; render a block'sregistryBlockswithhtmlPath. Seemotion_render_html§"Hyperframes registry blocks".reference/templates.md → render it with your own text/colors. Returns amotion_generate { templateId, slots, background }.jobId → thenjob_wait(orproject_add_motion_graphic { fromJob }forproject_add_designed_segment).split-panelEverything editable — text, colors, list items, and the background mode (/solid/transparent) — is controlled throughglass+slots. The full catalog (when to use each, what's editable) is in the "Motion graphics" section below.backgroundTemplates-first vs. custom depends on whether there's footage underneath:
- Mode A — graphics layered OVER existing footage (lower thirds, stat callouts, side panels, captions on a talking-head or screen recording): bundled templates are the default. Fast, on-brand, composite cleanly over the host. All the "templates first" guidance applies here.
- Mode B — a video built FROM SCRATCH where the motion graphics ARE the video (promo, explainer, intro/outro, product teaser, CTA piece — anything with NO source clip on the main track): default to fully custom, hand-authored scenes via
, NOT bundled templates. EXCEPTION — faceless videos are Mode B but IMAGE-driven, not HTML text scenes. If the ask is a "faceless" video (faceless YouTube / faceless short / narrated story or explainer with no face), do NOT author text motion-graphic scenes — that produces a title slideshow. Generate an IMAGE per beat + Ken-Burns + voiceover. See the dedicated "Faceless videos" section below. For the other Mode B pieces (promos/explainers): In from-scratch work templates read as generic and "templated" — exactly the wrong feel for a hero/marketing asset, which is usually the most visible, brand-defining thing the user makes. Loadmotion_render_htmlfirst — it is the design bar for this case: every scene a DISTINCT composition that SHOWS its point (don't headline features — depict them), one system never one layout, rotate layout archetypes. Then author each scene from the canonical shell inreference/promo-and-mg-videos.md. One NAMED alternative design system exists for Mode B: the whiteboard / hand-drawn explainer (reference/motion-philosophy.md) — paper canvas, SVG draw-on strokes, handwriting text reveal. Reach for it when the brief says "whiteboard / hand-drawn / sketch / doodle / handwritten", or when an abstract CONCEPT needs to be drawn (science, process, metaphor) and the tone wants friendly-approachable rather than premium-brand. Templates are still fine as building blocks inside an otherwise-custom piece (e.g. a transition between custom scenes) — just not the backbone. Reach for templates as the backbone of from-scratch work ONLY for a deliberately quick draft or when the user explicitly asks for speed over bespoke; if you do, SAY you're using templates for speed and offer the custom version.reference/whiteboard-style.mdFor Mode A, author custom HTML () only when no bundled template fits the brief — a bespoke one-off, an unusual layout, a brand-specific 3D treatment. That path is documented under "Custom motion graphics — HTML authoring"; loadmotion_render_htmlbefore authoring.reference/motion-philosophy.md
Video templates (storyboards): a whole video, not one overlay
When the user wants a finished short video (a channel intro, an episode open, a lesson opener) rather than a single overlay, use a storyboard — a multi-scene video template you fill in once:
→ see every storyboard: its id,motion_list_storyboards(youtube | podcaster | course), and itsaudience(the brief — text + color fields; color fields default to the workspace brand kit).params → the server renders each scene and concatenates them into ONE MP4. Returns amotion_generate_storyboard { storyboardId, params }. Omitted color params fall back to the brand kit (jobId); omitted required text params fail fast, so read the brief first.workspace_set_brand → thenjob_wait, same as a single template.project_add_motion_graphic { fromJob }Storyboards are slower than a single template (they render N scenes sequentially), so set a generoustimeout. Transitions between scenes are baked into each scene's own animation in v1 (the join is a hard cut). Prefer a storyboard over hand-composing severaljob_waitcalls when one fits the brief — it's one call and on-brand by default.motion_generate
优质视频编辑是一系列贴合平台观看场景的节奏打断操作。YouTube长视频观众已进入沉浸式状态——每5-8秒一次剪辑、电影级LUT滤镜会很合适;TikTok观众正在快速刷信息流——你只有3秒时间抓住他们的注意力,之后每一秒都需要视觉变化;LinkedIn观众处于工作场景——过于强烈的音效不合适;Loom观众只希望“去掉冗余内容”,不需要额外编辑。工具相同,参数设置却大相径庭。
Quickstart
平台预设(权威参考)
Reminder: examples below are the CLI path (preferred). Ifreturned empty, mentally translate each verb — e.g.command -v pandastudio→pandastudio system.status --jsonMCP tool,system_status→pandastudio project.add-zoom --id=… …with the same args.project_add_zoom
bash
undefined首先确定目标平台(参见【编辑决策:该问什么、该假设什么、绝对不能问什么】中的步骤1),然后应用对应行的所有默认设置——不要混合不同平台的设置。
| 参数 | | | | |
|---|---|---|---|---|
| 画幅比例 | 16:9 | 9:16 | 16:9 或 1:1 | 16:9 |
| 钩子内容截止时间 | 10秒 | 3秒 | 10秒 | —(无要求) |
| 片头/片尾卡片 | 仅当用户要求时添加(时长2-4秒) | 仅当用户要求时添加 | 仅当用户要求时添加(时长2-3秒) | 无 |
| 下三分之一字幕条 | 首次提及人物/产品时添加 | 不添加(垂直画幅空间不足) | 添加 | 不添加 |
| 缩放节奏 | 3-6次/分钟 | 6-12次/分钟 | 1-2次/分钟 | 0-1次/分钟 |
| 默认强调缩放时长 | 7秒 | 3秒 | 4秒 | 2秒 |
| 持续固定缩放时长(章节重构图) | 15秒 | — | 8秒 | — |
| 代理对录屏片段添加缩放 | 绝对禁止(系统会根据光标追踪自动添加) | 绝对禁止 | 绝对禁止 | 绝对禁止 |
| 缩放音效音量 | 1.0(快速 swoosh 音效) | 1.0(快速 swoosh 音效) | 0.5(或设为 | |
| 填充词/静音片段移除 | 开启 | 开启 | 开启 | 开启(激进模式——最小静音时长300ms) |
| 变速片段(B-roll) | 1.5-2× | 2-3× 或直接删除 | 1.25-1.5× | 无 |
| LUT滤镜预设 | 根据内容类型选择(强度0.5-0.8) | | | 无 |
| 背景音乐 | 仅当用户要求时添加(音量0.15) | 仅当用户要求时添加(音量0.30) | 无 | 无 |
| 字幕启用状态 | 不内嵌——改用关键词弹出(数据显示:9个调研的长视频均未内嵌语音字幕;参见longform-styles.md的LF4规则) | 必须启用 | 启用 | 可选 |
| 字幕模板 | —(使用关键词弹出覆盖层,而非字幕模板;若用户坚持要字幕:用 | | | |
| 导出画质 | | | | |
YouTube长视频的LUT滤镜按内容类型选择(其他平台使用上表中的固定预设):
| 内容类型 | 滤镜预设 | 强度 |
|---|---|---|
| 技术教程/SaaS演示 | | 0.7 |
| 电影感vlog | | 0.9 |
| 教育类/中性风格 | | 0.5 |
| 氛围感叙事 | | 0.7 |
| 旅行/生活方式 | | 0.7 |
1. Confirm the server is reachable AND the user has a license.
创作者风格覆盖(当用户指定风格时)
MCP equivalent: call system_status
with no args.
system_status—
pandastudio system.status --json
当用户说“像Ali Abdaal的视频风格”/“MKBHD风格”/“MrBeast风格”等时,先应用对应基础平台预设,再叠加以下覆盖设置。这些设置是在平台默认之上添加,而非替代。未列出的风格则回归基础平台预设。
| 风格 | 基础平台预设 | 节奏 | LUT滤镜 | 音乐 | 字幕模板 | 动态图形节奏及说明 |
|---|---|---|---|---|---|---|
| Ali Abdaal(生产力/书评/教程类长视频) | | 每3-5秒一次视觉变化;激进移除填充词和静音片段 | | 温暖氛围/lo-fi风格(音量0.15-0.20) | | 片头标题卡片(停留3秒)· 0:04-0:09添加主播下三分之一字幕条 · 重点观点处添加3-4个右侧栏概念标注 · 若视频提及数据,添加1次全屏数据展示 · 片尾4-6秒停留卡片,显示“点赞&订阅”及账号名闪烁效果 |
| MKBHD(科技评测/产品类长视频) | | 每4-6秒一次变化——节奏舒缓,产品画面留足展示时间 | | 轻快科技评测背景音(音量0.20) | | 简洁片头文字标识(停留2秒)· 仅在首次提及产品时添加简洁下三分之一字幕条 · 产品画面上的数据展示使用深色背景+铬渐变数字 · 片尾:产品回顾卡片+订阅提示 |
| MrBeast(特技/挑战/高留存率视频) | | 每2-3秒一次变化——节奏极快,类似Shorts | | 戏剧性管弦乐背景音(音量0.30) | | 每约5秒添加大尺寸铬渐变动态文字 · 频繁全屏数据展示并附带数字动画 · 若视频有悬念,添加倒计时覆盖层 · 片尾:“下期预告”卡片,完整停留6秒 |
| Veritasium / Kurzgesagt实景风格(科学/教育类长视频) | | 每5-7秒一次变化——节奏舒缓,给图表留足阅读时间 | | 氛围/管弦乐背景音(音量0.12) | | 动态图形形式的解释性图表(带标注的SVG,使用 |
| Vox / Johnny Harris(解释性/随笔类长视频) | | 每4-6秒一次变化——叙事驱动 | | 电影感背景音(音量0.18) | | 每个章节开头添加章节卡片(粗体铬渐变章节标题)· 地图/时间线/图表动态图形 · 右侧栏添加引用标注 · 片尾:制作人员名单卡片+下期视频预告 |
2. Discover what's available — never guess command names.
锚定规则——所有基于字幕的区域必须锚定
MCP equivalent: call system_list_commands
.
system_list_commands—
pandastudio commands
本规则适用于五类区域:缩放、动态图形、下三分之一字幕条、标注、音频覆盖层(作为音效时,背景音乐除外)。
3. Render a motion graphic from a bundled template (the primary path).
问题背景
See the "Motion graphics" section for the full catalog.
—
JOB=$(pandastudio motion.generate
--templateId=creator-card
--slots='{"headline":"live demo","eyebrow":"now","brandColor":"#2563EB"}'
--aspectRatio=16:9
--json | jq -r '.data.jobId')
--templateId=creator-card
--slots='{"headline":"live demo","eyebrow":"now","brandColor":"#2563EB"}'
--aspectRatio=16:9
--json | jq -r '.data.jobId')
pandastudio job.wait --id="$JOB" --json | jq '.data.job.result'
区域位置存储为编辑后时间——即修剪后的播放时间。当用户(或你)执行、、或时,会添加新的修剪区域,编辑后时间映射会发生偏移,任何基于之前编辑后时间设置/的区域都会偏离原本放置的时间点。比如你在“subscribe”这个词的位置添加了“点赞订阅”下三分之一字幕条,但如果之后删除了该词之前的800ms填充词,字幕条会提前800ms播放。
transcript.remove-fillerstranscript.remove-silencestranscript.delete-wordstranscript.find-replacestartMsendMs4. Add the rendered clip to the timeline at the playhead / a given time.
解决方案:--anchorSourceMs
参数
--anchorSourceMspandastudio project.add-motion-graphic --id="$PROJECT" --fromJob="$JOB" --durationMs=4500
That's the whole loop: probe → discover → call → (if async) wait. Every richer workflow is a composition of those four steps.
> **`job.wait` timeouts are not failures.** Default 5 min, hard cap 30 min. If the wait returns `{ job, timedOut: true }`, the job is **still running** — call `job.wait` again with the same id to keep polling. NEVER treat `timedOut: true` as a render failure; the underlying render keeps going regardless of whether anyone's waiting on it. For heavy 30s @ 1080p motion-graphic renders, expect 8–15 minutes — pass `--timeoutMs=600000` or higher up front, or re-poll until the result lands.当你从字幕单词的源时间推导/时,将同一个值作为传入。区域会记录其锚定的原始录制时间点,后续任何修剪/变速编辑都会自动将区域的编辑后位置重新锚定到原始时间点——下三分之一字幕条会始终固定在“subscribe”这个词的位置,无论你做了多少修剪。
atMsstartMs--anchorSourceMsv1.35.0及以上版本:所有区域在创建时自动锚定,即使未传入。 当调用者未显式提供锚定时,系统会自动从解析后的编辑后--anchorSourceMs反向计算源时间锚点(通过atMs)。直接按editedToSource放置的区域,在后续移除静音、填充词或删除单词时,也会像基于字幕锚定的区域一样保持位置准确。不过当你能获取到字幕单词时,仍建议传入atMs——这能让代理的意图更清晰,避免往返转换编辑时间的额外步骤。对于v1.35.0及以后保存的项目,“我添加了覆盖层,然后修剪了静音片段,现在它播放位置不对”的问题已彻底解决。旧项目(schemaVersion < 4)会在首次修改时自动迁移:所有无锚定的区域会根据当前位置自动回填源时间锚点。--anchorSourceMs锚点会保留作为可选关闭项。type: "free"
Before any tool call: license check
支持锚定的操作(从字幕选位置时必须使用)
Always run first. Read the block:
pandastudio system.status --jsonlicense| Field | What it means |
|---|---|
| Full surface available. |
| Active trial. Full surface available. |
| Trial expired, no license. Only |
If the call fails with a connection error, the CLI auto-launches PandaStudio and waits up to 60 s. If it fails with , the on-disk credentials at rotated mid-flight — wait 2 s and retry once.
invalid or missing bearer token~/.config/pandastudio/{token,port}| 操作 | 锚定参数 | 必填场景 |
|---|---|---|
| | 当 |
| | 当 |
| | 当 |
| | 当 |
| | 当覆盖层是绑定到单词的音效时必须传入。绝对不要用于背景音乐——背景音乐应保持自由浮动(固定在编辑后时间线的某个位置,不锚定到内容)。 |
Workspaces (v1.19+)
自由浮动场景也可接受
PandaStudio is multi-workspace as of v1.19. Every / / / / query operates inside the active workspace — the one listed in . Users (typically agencies) separate clients into their own workspaces so credentials, exports, and YouTube connections never cross-contaminate.
project.*export.*motion.*caption.*audio.*workspace.currentRight after , check the workspace context:
system.statusbash
pandastudio workspace.list --json | jq '.data | { current: .currentWorkspaceId, count: (.workspaces | length), cap: .limit }'The returned is:
limit.max- — Starter plan or Trial
1 - — Creator plan
3 - — Team plan (unlimited)
null
Switching workspaces:
bash
undefined当用户明确要求按编辑后时间放置区域(比如“在时间线最后5秒添加片尾卡片”),则无需传入锚点。无论后续如何编辑,区域都会保持在你放置的位置。
Get the id of a specific client's workspace
anchorSourceMs
是全局源时间(从录制开始的毫秒数)
anchorSourceMsWS=$(pandastudio workspace.list --json | jq -r '.data.workspaces[] | select(.name == "ACME Agency — Client A") | .id')
pandastudio workspace.switch --id=$WS --json
在多片段项目中,需要将片段内的偏移量加上前面所有片段的,转换为全局源时间后再传入。可使用验证源时间位置对应编辑后时间线的哪个点。
sourceDurationMs--anchorSourceMstimeline.source-to-editedEvery subsequent query now operates inside that workspace.
手册已按节奏优先、再添加区域的顺序编排
**Creating a workspace:**
```bash即使不使用锚点,这样的顺序也是安全的——区域会落在修剪后的时间线上。但:任何中途重新编辑(比如“实际上,删掉关于X的部分”)会导致无锚定区域无声偏移。只要位置来自字幕单词,就始终添加锚点,即使遵循了手册顺序。多传入一个参数的成本很低,但能保证迭代过程中的准确性。
Agencies: one workspace per client.
操作手册(按顺序执行——不要调整顺序)
pandastudio workspace.create --name="ACME Agency — Client A" --switchTo=true --json
bash
undefinedIf the plan cap is hit, the response looks like:
0. 确定项目+目标平台预设
{ "ok": false, "error": "Your Starter plan allows 1 workspace. Upgrade to Creator to create more.",
—
"details": { "code": "workspace_limit_reached", "upgradeTo": "Creator" } }
—
Tell the user to upgrade at writepanda.ai/#pricing; do NOT retry.
—
**Deleting (destructive):** call `workspace.contents` first so you can show the user what will be lost, then `workspace.delete`. Projects' on-disk `.pandastudio` files stay — only the library rows disappear. YouTube-published videos stay on YouTube (we can't delete those); we only drop the local connection + cache.
```bash
pandastudio workspace.contents --id=$WS --json | jq '.data.counts'ID=$(pandastudio project.current --json | jq -r '.data.project.id // empty')
[ -z "$ID" ] && ID=$(pandastudio project.list --json | jq -r '.data.projects[0].id')
{ "projectCount": 12, "exportCount": 4, "publishedVideoCount": 3 }
根据平台预设设置$PROFILE:youtube-long | shorts | linkedin | loom
Confirm with user before:
根据预设推导$ASPECT:
—
youtube-long | linkedin | loom → 16:9
—
shorts → 9:16
pandastudio workspace.delete --id=$WS --json
**Don't quietly switch workspaces mid-task.** If you need to operate in a different workspace than the one the user opened, confirm with them first. Crossing client boundaries silently is how agency relationships break.pandastudio project.set-aspect-ratio --id=$ID --aspect=$ASPECT
⚠ When given a project id with no other context
1. 节奏调整——默认清理流程(参见【编辑决策】中的默认编辑流程,包含必填步骤和结果报告规则)。完整执行所有步骤——没有可选步骤。audio.clean是异步操作;在导出前(步骤5)等待其完成。首次读取会获取字幕(步骤2需要);后续读取传入--includeTranscript=false。
If the user hands you a project id (in chat, in a CSV, in a webhook payload) and you don't know which workspace it belongs to, always run FIRST, before any read/edit/export/publish:
project.locatebash
RES=$(pandastudio project.locate --id=$PID --json)pandastudio project.read --id=$ID --json
pandastudio transcript.transcribe --id=$ID # 若已转录则跳过
AUDIO_CLEAN_JOB=$(pandastudio audio.clean --id=$ID --json | jq -r '.data.jobId // empty') # 异步操作
pandastudio transcript.remove-fillers --id=$ID # 移除填充词+重复内容
{ "data": { "id": ..., "filePath": ..., "workspaceId": ..., "workspaceName": "Client A",
错误片段+重复短语:find-issues是只读操作——必须手动删除废弃的wordIds(保留最新片段)。跳过删除步骤等于没做任何修剪。
"isInActiveWorkspace": false } }
—
IN_ACTIVE=$(echo "$RES" | jq -r '.data.isInActiveWorkspace')
WS_NAME=$(echo "$RES" | jq -r '.data.workspaceName')
if [ "$IN_ACTIVE" != "true" ]; then
STOP. Do not silently switch. Ask the user.
"This project lives in workspace '$WS_NAME', current is '<X>' — switch?"
fi
**Why this matters:** `project.read` happily resolves a project from any workspace, but `export.publish-youtube`, `media.generate-image`, `export.generate-thumbnail`, and `youtube.list-accounts` all use the **active workspace's** credentials. Editing project A while workspace B is active and then publishing → the video lands on Client B's YouTube channel. **The worst kind of mistake.**
**Every `project.read` response now also carries the workspace fields** (`workspaceId`, `workspaceName`, `isInActiveWorkspace`) — so even if you skipped `project.locate`, you can still detect the mismatch from the read response and bail before mutating anything. But `project.locate` is cheaper (no project body) and clearer in intent — call it first when working from a bare id.
**Hard rule: never call `export.publish-youtube` without confirming `isInActiveWorkspace === true` for the project being published.** If the user asks you to publish a project in a different workspace, walk them through the explicit switch:
```bash
pandastudio workspace.switch --id=$TARGET_WS --jsonISSUES=$(pandastudio transcript.find-issues --id=$ID --json | jq -c '.data.issues')
DROP=$(echo "$ISSUES" | jq -c '[.[] | select(.type=="duplicate-take" or .type=="adjacent-repeat") | .wordIds[]]')
[ "$DROP" != "[]" ] && pandastudio transcript.delete-words --id=$ID --wordIds="$DROP"
SILENCE_MS=$([ "$PROFILE" = "loom" ] && echo 300 || echo 500)
pandastudio transcript.remove-silences --id=$ID --thresholdMs=$SILENCE_MS # 绝对不要跳过;参数是阈值毫秒数(500ms是UI默认值)
Then re-run any pre-flight that depends on workspace state
2. 强调效果——缩放(loom平台跳过)。节奏来自平台预设表。
(license check, youtube account list, replicate key check)
—
undefinedProject-look defaults (v1.49.1+)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
—
硬规则1——绝对不要给录屏片段添加缩放。
—
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
—
PandaStudio会根据录制时的光标追踪数据,自动给录屏片段添加缩放。手动添加会导致同一时间点叠加多个缩放,造成视觉混乱。
Save a workspace's preferred look once so every NEW project and fresh recording starts from it — the user doesn't re-pick a background / caption style each time. Per-workspace. Covers background (), , and editor (padding, shadow, corner radius, blur). The editor also exposes this as a "Save as default for new projects" button.
wallpapercaptionSettingseditorDefaultsbash
undefinedRead the current defaults (null = none set)
对于project.read返回的每个片段:
—
- 若片段包含webcamPath → “双画面”模式(录屏+画中画摄像头)。该片段已存在追踪缩放。跳过。
—
- 若片段是纯摄像头画面(无webcamPath且mediaPath是摄像头录制内容)→ 可安全添加强调缩放。
—
- 若片段是纯录屏(无webcamPath,mediaPath是录屏内容)→ 仍存在追踪数据,会自动添加缩放。跳过。
pandastudio workspace.get-project-defaults --json
Set them — pass any subset; unknown fields are dropped.
经验法则:若project.read返回的片段包含webcamPath且已有zoomRegions,这些是基于追踪数据的缩放——绝对不要修改该片段的缩放设置。仅在纯摄像头片段(无webcamPath、无预先存在的缩放区域)上添加缩放。
pandastudio workspace.set-project-defaults
--defaults='{"wallpaper":"/wallpapers/wallpaper5.jpg","captionSettings":{"enabled":true,"templateId":"editorial"},"editorDefaults":{"padding":18,"borderRadius":8}}'
--json
--defaults='{"wallpaper":"/wallpapers/wallpaper5.jpg","captionSettings":{"enabled":true,"templateId":"editorial"},"editorDefaults":{"padding":18,"borderRadius":8}}'
--json
Clear them
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
—
硬规则2——缩放时长下限为6秒,而非1.5-3秒。
—
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
—
优质YouTube缩放会覆盖完整的观点或剪辑片段。1.5秒或3秒的缩放会显得突兀。默认设置:
—
- 强调切入缩放(摄像头拉近到某个单词/观点):
—
durationMs=6000至8000 (原1500ms设置不合适)
—
- 持续固定缩放(摄像头拉近到某个主题并保持整个小节):
—
durationMs=10000至20000
—
- 揭秘时刻(戏剧性、大声音效):durationMs=6000秒最低
—
摄像头强调使用深度3(适度);仅在戏剧性揭秘时刻使用深度5。不要在5秒内叠加多个缩放——给观众留足适应时间。
pandastudio workspace.set-project-defaults --defaults=null --json
Use when the user says things like "use this background for all my videos" or "always start new projects with these captions". Applies to projects/recordings created AFTER it's set — it doesn't retro-edit existing projects.Brand kit — set it, or auto-capture it from a URL (v1.84+)
镜头选择:扫描字幕寻找有力观点、具体数字、主观表述(“最佳”“彻底改变”“大多数人不知道”)、转折点(“现在”“最终”“但关键是”)。目标是让用户的眼睛想要聚焦。不要给每个UI操作词(“点击”“选择”)添加缩放——旧规则不适用于高留存率编辑。
The workspace brand kit (colors primary/accent/ink/background, display/body fonts, logo, voice) feeds brand-aware captions, motion graphics, lower-thirds, and thumbnails. Two ways to fill it:
bash
undefinedManual: set any subset.
关键:当atMs
来自字幕单词时,必须传入--anchorSourceMs
。否则,只要添加任何修剪(步骤1总会添加修剪:移除填充词、移除静音片段),缩放就会偏离原本的时间点。锚点会将缩放绑定到原始录制时间点,使其在每次修剪/变速更改时自动重新锚定。
atMs--anchorSourceMs—
强调切入——适度缩放,覆盖完整观点
pandastudio workspace.set-brand --brand='{"name":"Acme","colors":{"primary":"#2563EB","ink":"#111827","background":"#FFFFFF"},"typography":{"display":"Inter"}}' --json
pandastudio project.add-zoom --id=$ID --clipId=$CLIP_ID \
--atMs=<wordStartMs> --anchorSourceMs=<wordStartMs> \
--durationMs=7000 --depth=3
Auto: pull the real brand straight off a website. Runs HyperFrames capture,
持续固定缩放——重新构图到某个主题并保持整个小节
classifies the site's actual colors/fonts/logo, and MERGES them into the kit
—
(your hand-set fields survive). ASYNC — poll job.wait; first run downloads the
—
capture CLI so use a long timeout.
—
JOB=$(pandastudio workspace.capture-brand --url=https://acme.com --json | jq -r '.data.jobId')
pandastudio job.wait --id="$JOB" --timeoutMs=300000 --json | jq '.data.job.result.brand'
Reach for `workspace.capture-brand` whenever the user says "use my brand", "make it match my site", or you're onboarding a new client and only have their URL — it beats asking them to type six hex codes. Needs network access. After it lands, the classified brand is a starting point; if the user corrects a color, apply it with `workspace.set-brand`.pandastudio project.add-zoom --id=$ID --clipId=$CLIP_ID \
--atMs=<sectionStartMs> --anchorSourceMs=<sectionStartMs> \
--durationMs=15000 --depth=3
Organising projects, renaming, transcription languages
揭秘时刻(戏剧性,带音效)——谨慎使用,每视频最多1-2次
Folders, , project-look defaults, transcription-language switching (Parakeet/Whisper), and transcribing a standalone file → text/SRT/VTT. Full detail: .
project.renamereference/projects-and-transcription.mdpandastudio project.add-zoom --id=$ID --clipId=$CLIP_ID \
--atMs=<ms> --anchorSourceMs=<ms> \
--durationMs=6000 --depth=5 \
--soundUrl=bundled:sound/dramatic-whoosh --soundVolume=0.7
Recording the screen yourself (agent-driven, v1.86+)
3. 打磨优化——按平台预设跳过对应部分:
—
- shorts
:不添加下三分之一字幕条(垂直画幅空间紧张)
shorts—
- loom
:完全跳过3b、3c
loom—
注意:3a(片头/片尾)和3d(背景音乐)是可选操作——仅当用户明确要求添加片头/片尾或音乐时才执行。它们不属于任何平台预设的默认“编辑我的视频”流程。
—
3a. 片头/片尾卡片——仅当用户明确要求时添加
You can START and STOP a high-quality screen recording directly — no UI, no
user in the loop. This is the full-quality alternative to a browser's built-in
capture: drive a web app (or anything on screen) yourself, record it into
PandaStudio, then edit and export. macOS/Windows only.
bash
undefined#(比如“添加片头”“添加片尾卡片”“用标题开头”)。不要在单纯的“编辑这个”请求中添加。若用户要求:最快的方式是通过motion.generate使用模板;仅当需要定制片头时,才参考reference/motion-philosophy.md第7节编写自定义HTML。
if [ "$USER_ASKED_FOR_INTRO" = "1" ]; then
JOB=$(pandastudio motion.render-html \
--htmlPath=/tmp/intro-title.html \
--durationMs=3000 --json | jq -r '.data.jobId')
FILE=$(pandastudio job.wait --id=$JOB --json | jq -r '.data.job.result.outputPath')
pandastudio project.add-motion-graphic --id=$ID --file=$FILE --durationMs=3000 --atMs=0
fi
creator-card1. (optional) see what you can target — displays + windows
3b. 首次提及人物/产品时添加下三分之一字幕条(shorts/loom平台跳过)
—
一次调用即可渲染lt-*铭牌并放置(异步任务)。
pandastudio recording.list-sources --json
if [ "$PROFILE" = "youtube-long" ] || [ "$PROFILE" = "linkedin" ]; then
JOB=$(pandastudio project.add-lower-third --id=$ID \
--name="<姓名>" --title="<职位>" --atMs=<ms> --anchorSourceMs=<ms> \
--json | jq -r '.data.jobId')
pandastudio job.wait --id=$JOB
fi
→ { displays:[{id:"screen:1:0",name:"…",primary:true}], windows:[{id:"window:123:0",name:"Google Chrome — …"}] }
3c. LUT滤镜(使用平台预设表;youtube-long平台使用内容类型子表)
2. start (defaults to the primary display; pass --source to pick a window/display)
通过project.set-clip-lut应用到所有片段。
—
loom平台完全跳过。
—
3d. 背景音乐——仅当用户明确要求添加音乐时执行
pandastudio recording.start --json # whole primary display
pandastudio recording.start --source="window:123:0" --json # just that Chrome window
#(比如“添加背景音乐”“在下面加个音轨”)。不要在单纯的“编辑这个”请求中添加。若用户要求,使用平台预设音量(youtube-long 0.15,shorts 0.30),并允许用户选择/切换音轨。
if [ "$USER_ASKED_FOR_MUSIC" = "1" ]; then
VOL=$([ "$PROFILE" = "shorts" ] && echo 0.30 || echo 0.15)
pandastudio project.add-audio --id=$ID \
--path=bundled:music/corporate-underscore --volume=$VOL --fadeIn=1000 --fadeOut=2000
fi
→ { recordingId }
4. 无障碍支持——按平台预设添加字幕
3. …now do the thing you want to capture (click through the app, etc.)…
—
4. stop — finalizes the MP4 AND creates an editable project by default
—
pandastudio recording.stop --name="ACME tutorial" --json
if [ "$PROFILE" != "loom" ]; then
pandastudio caption.toggle --id=$ID --enabled=true
TEMPLATE=$(case "$PROFILE" in
shorts) echo "neon";;
linkedin) echo "minimal";;
youtube-long) echo "bold";;
esac)
pandastudio caption.set-template --id=$ID --templateId=$TEMPLATE
全大写样式(Shorts常用):强制任何模板使用大写。
pandastudio caption.set-style --id=$ID --uppercase=true
(uppercase=false会关闭模板自带的大写,比如editorial模板)
隐藏视频部分区域的字幕。字幕启用后默认全程显示,这些操作会设置例外场景——用于字幕会遮挡屏幕内容的情况(UI演示、屏幕文字、下三分之一字幕条)。
时间为编辑后时间线的毫秒数。重叠区域是允许的。
pandastudio project.add-caption-region --id=$ID --atMs=12000 --durationMs=5000
删除隐藏区域(ID来自project.read → editor.captionRegions[].id):
pandastudio project.remove-caption-region --id=$ID --regionId=caption-hide-1
fi
→ { screenPath, durationMs, projectId, projectPath, projectCreated:true }
静音视频部分区域的音频。会在指定时间段内静音主语音/录屏轨道——背景音乐和音效覆盖层仍会播放(与编辑器操作一致)。
—
时间为编辑后时间线的毫秒数;durationMs控制静音时长。
—
用于咳嗽声、需要隐藏的姓名、或想要静音但不删除画面的死区(删除操作会同时剪掉画面;静音会保留画面但去掉声音)。
—
重叠区域是允许的。若要同时剪掉画面和声音,使用修剪操作。
—
pandastudio project.add-mute-region --id=$ID --atMs=12000 --durationMs=3000
—
删除静音区域(ID来自project.read → editor.muteRegions[].id):
—
pandastudio project.remove-mute-region --id=$ID --regionId=mute-1
—
4.5. 帧验证——必须执行。导出前务必检查。
—
对每个渲染的动态图形MP4以及完整合成的草稿版本(preview.show后,在关键时间点提取帧)执行motion.verify-frames。将每个PNG作为多模态图像读取,并对照motion-philosophy第4节的预检清单确认:
—
- 没有人脸被裁剪/文字溢出/空白帧
—
- 字幕与对应单词同步
—
- 动态图形未遮挡主播面部(模式C)或屏幕区域(模式B)
—
- 铬渐变文字正常渲染(不是纯白色)
—
- 每个关键镜头都能看到网格+暗角+颗粒效果
—
若任何帧不符合要求,迭代修改并重新验证。即使是“快速编辑”也不要跳过此步骤——这是“能用”和“好用好看”的区别。
Then edit the returned project like any other: `transcript.transcribe` →
`transcript.remove-fillers` → `project.add-zoom` on the key clicks →
`media.generate-narration` for a voiceover (`project.add-audio`) → `export.start`.
Notes:
- **Permission:** screen capture needs the one-time OS Screen Recording grant.
It is already granted for anyone who has ever recorded in the app, so this
runs with zero interaction. On a brand-new install that never recorded, the
first `recording.start` returns a clear "grant Screen Recording and retry"
error instead of hanging — surface that to the user; you cannot grant it for
them.
- **One at a time.** `recording.start` fails if a recording is already active —
call `recording.stop` first.
- **No mic on this path.** Only screen (and optional `--systemAudio=true`).
Record clean, then add narration with `media.generate-narration`.
- `recording.stop --createProject=false` just finalizes the MP4 and returns
`screenPath` if you want to compose `project.new --withMedia=…` yourself.Shorts: turning an exported video into vertical clips
🛑 验证是独立的、尽力而为的步骤——绝不会“导致编辑失败”。
—
每个project.*修改操作(add-clip、add-motion-graphic、add-audio等)在工具返回ok时,会立即提交到.pandastudio文件。验证步骤(渲染帧,然后将PNG作为多模态图像读取)是在修改之后执行的质量检查,不属于编辑流程的一部分。因此:
—
• 顺序很重要:先执行修改操作,确认返回ok,再执行验证。编辑内容在读取帧之前就已持久化。
—
• 若帧读取步骤失败——模型在图像上超时、“N分钟无输出”、速率限制、“工具读取失败”——编辑内容仍已生效。不要将任务报告为失败。应报告:“已完成——<编辑操作>已添加到时间线。我无法完成视觉检查(模型未响应);可重试检查或切换模型。”因为后续视觉读取卡住就把已提交的编辑报告为失败是错误的。
—
• 保持读取操作轻量化,避免模型卡住:仅在关键时间点验证单个渲染帧(一张PNG),不要渲染多帧的大尺寸接触表。一张小图像是低成本的视觉调用;大尺寸接触表最容易导致模型超时。
Discover shots (), fork the source project per shot (), the 9:16 vertical playbook, drift detection, and batch N shorts. Full detail: . To make a short actually RETAIN — "make it engaging/viral", "edit like Hormozi / Ali Abdaal / a podcast clip" — load : four evidence-based recipes with the seven retention laws, quantified caption/zoom/overlay parameters, and a render-frame verification pass. Load alongside it — exact command shapes plus a hyperframes starter shell, so you never grep schemas or other reference files mid-edit. For edits that should RETAIN (not just play clean), load — quantified from a 9-video measured study (Ali Abdaal / MKBHD / Fireship, July 2026): three recipes (educator-pip, product-review, dev-explainer), the two-level rhythm, keyword pops instead of burned captions, in-edit segmentation, and ending liturgy.
export.find-shotsproject.fork-from-shotreference/shorts.mdreference/shorts-styles.mdreference/shorts-cheatsheet.mdyoutube-longreference/longform-styles.mdpandastudio preview.show --id=$ID # 让项目渲染完整草稿
Shorts layout: full-frame vs camera-corner-over-blur
然后在每个生成的动态图形MP4的关键时间点验证:
—
pandastudio motion.verify-frames --videoPath=/tmp/motion-intro.mp4 \
—
--timestamps='[0.3,1.0,1.8,2.7]' --json
—
读取每个返回的帧。若任何帧不符合要求,修改+重新渲染+重新验证。
—
5. 导出——按平台预设选择画质。仅在帧验证通过后执行。
—
首先:等待步骤1中后台运行的audio.clean任务完成。此时它几乎肯定已完成(30-60秒,而其他步骤耗时约90秒以上),因此会立即返回结果。
For a camera-only clip in a 9:16 project, is the one-click layout picker:
project.set-shorts-layoutbash
undefined[ -n "$AUDIO_CLEAN_JOB" ] && pandastudio job.wait --id="$AUDIO_CLEAN_JOB"
QUALITY=$([ "$PROFILE" = "loom" ] && echo "standard" || echo "high")
pandastudio export.start --id=$ID --quality=$QUALITY --json | jq -r '.data.jobId' | \
xargs -I {} pandastudio job.wait --id={}
undefinedCamera shrinks to a draggable bottom-right tile over a blurred copy of itself
性能优化——缩短总耗时
pandastudio project.set-shorts-layout --id=$PID --layout=camera-corner
动态图形渲染是耗时主要来源(每个场景约20-45秒);其他操作耗时可忽略不计。优化手段:
- 渲染是串行的——一次只启动一个,
motion.render-html完成后再启动下一个。 系统有单渲染互斥锁,并发启动第二个渲染会返回job.wait(并行渲染会共享GPU/临时文件/无头浏览器状态,导致系统卡住)。不要同时启动所有渲染任务。{ ok:false, error:"RENDER_BUSY" } - 后台运行(转录后立即获取其jobId;仅在
audio.clean前执行export.start)。这与渲染任务并行是安全的——串行限制仅针对渲染任务之间。job.wait - 完整渲染前先用预检HTML——在
motion.screenshot位置生成约2秒的截图,比浪费20-45秒进行完整渲染更高效。它会内联GSAP并跳转到暂停的时间线,因此能预览动画帧(而非静态JS加载前的DOM)。--atMs=<场景中间> - 减少重复读取:首次后传入
project.read,并复用每个修改操作返回的--includeTranscript=false对象,而非重新读取。{ project }
Camera fills the frame (clears the transform + backdrop)
反模式(所有平台都不要这么做)
pandastudio project.set-shorts-layout --id=$PID --layout=full
`camera-corner` sets the main-clip transform AND a `blur-self` backdrop together; reposition the tile afterward with `project.set-screen-transform` (`x`/`y` are canvas-fraction center offsets, `scale` the tile size). The two pieces are also independently settable: `project.set-backdrop --mode=blur-self|wallpaper` controls only the fill behind a scaled-down video (invisible while the video fills the frame). For a **screen-recording (screen+camera)** clip, don't use these — use `project.set-webcam-layout --preset=picture-in-picture`, which already gives screen-fills-with-camera-corner. The blurred self-fill renders identically in preview and export.- 同一时间点叠加3种效果(缩放+下三分之一字幕条+动态图形)——视觉噪音
- 一个项目使用多个LUT滤镜——从平台预设表中选一个即可
- 每次剪辑都加音效——限制为每15-30秒一次有意义的音效(shorts平台可放宽到每5-10秒一次)
- 对语音片段变速——仅用于准备工作/B-roll/滚动内容
- 片头Logo时长超过5秒(任何平台)——留存率骤降
- 询问用户要移除哪些填充词——这是完全安全的操作,直接执行即可
- 将youtube-long预设应用到shorts项目——画幅比例错误、音乐音量过低、字幕不够醒目、节奏太慢
- 给loom项目添加动态图形——破坏“快速更新”的氛围
Active-speaker auto-reframe: landscape multi-person → vertical (v1.70+)
触发短语——启动本手册的场景
When you crop a landscape source with more than one person (a talk show,
interview, podcast panel, any director-cut footage) into 9:16, a single static
cover-crop lands on the gap between people in wide shots and off-face in
close-ups of whoever isn't centered. is a tracked
virtual camera that fixes this — the same approach Opus Clip / Vizard use:
project.auto-reframe- Shot detection (ffmpeg scene cuts) segments the source.
- Dense face tracking — MediaPipe FaceLandmarker (bundled, offline) samples ~7fps, with adaptive tiling so small/far faces in wide shots are still found. Detections are associated into per-person tracks.
- Audio active-speaker — on multi-person shots it frames whoever is talking (mouth-open × speech-energy), not the biggest face.
- Smoothed camera — the crop pans to follow the subject within a shot (with a dead-band hold + a safe-zone clamp so the face never leaves frame) and cuts at shot boundaries.
bash
undefined以下短语均触发上述编辑工作流。不要让用户进一步解释——直接确定平台预设+风格,告知计划并执行。
| 用户表述 | 对应预设 |
|---|---|
| "编辑这个" / "打磨这个" / "让它更吸引人" / "让它准备好发布" | |
| "做好YouTube发布准备" / "制作YouTube视频" / "为YouTube编辑" | |
| "做成Shorts" / "TikTok风格" / "Reel" / "垂直画幅" / "9:16" | |
| "用于LinkedIn" | |
| "Loom风格" / "内部更新" / "只去掉冗余内容" | |
| "像Ali Abdaal那样编辑" / "Ali Abdaal风格" / "教程风格" / "生产力视频" | |
| "MKBHD风格" / "科技评测风格" / "产品评测" | |
| "MrBeast风格" / "高留存率" / "挑战视频" / "最大化吸引力" | |
| "Veritasium风格" / "教育类" / "Kurzgesagt氛围" / "解释性视频" | |
| "Vox风格" / "随笔类" / "叙事风格" / "Johnny Harris风格" | |
若用户未指定风格且未指定平台,安全默认是无风格覆盖——这是最常见的场景。
youtube-longReframe every landscape clip — tracks + follows the active speaker.
一次性执行模式
pandastudio project.auto-reframe --id=$PID --json
触发短语+平台预设/风格确定后,用一句话告知计划并执行。在动态图形步骤前自动加载。完整执行工作流,包括必须的帧验证环节。不要让用户批准单个步骤。
reference/motion-philosophy.md我会将这段视频编辑为Ali Abdaal风格的YouTube长视频——激进移除填充词和静音片段,3-5秒一次视觉变化,重点观点处添加4个右侧栏概念标注,1次全屏数据展示,LUT滤镜强度0.5,温暖氛围音乐音量0.15,modernVibrant字幕垂直位置Y=0.85,以及5秒片尾CTA卡片。动态图形遵循motion-philosophy规范(铬渐变、网格+暗角+颗粒效果)。导出前会进行帧验证。耗时约5分钟。bold
我会将这段视频编辑为Shorts短视频——激进节奏(3秒内抓住注意力,6-12次缩放/分钟),LUT滤镜满强度,modernVibrant字幕位置上移,音乐音量30%。不添加片头卡片或下三分之一字幕条——它们不适合垂直画幅。导出前会进行帧验证。耗时约2分钟。neon
我会将这段视频编辑为MrBeast风格的YouTube视频——2-3秒一次视觉变化(节奏极快),LUT滤镜强度0.8,戏剧性管弦乐背景音音量0.30,超大尺寸配色warmSunset字幕,每5秒添加铬渐变动态文字,全屏数据展示,6秒片尾预告卡片。动态图形遵循motion-philosophy规范。导出前会进行帧验证。耗时约6分钟。neon
不要让用户微管理步骤选择。平台预设表+创作者覆盖设置+motion-philosophy就是答案。若确实需要用户输入(比如指定风格但缺少明显的品牌参考、下三分之一字幕条缺少人物姓名),将所有问题收集到一条消息中——不要逐个询问。
→ { reframed: [{clipId, shots, shotsWithFace}], skipped: [...] }
应用内代理会话——观察与停止
One clip only, or tune shot sensitivity / punch-in:
—
pandastudio project.auto-reframe --id=$PID --clipId=clip-1 --threshold=0.3 --minShotMs=500 --zoom=1.3 --json
桌面应用内置了聊天代理。两个操作(应用版本≥1.60)允许外部代理查看和控制这些会话:(返回会话ID、标题、时间戳;当内置代理服务器未启动时返回——不会主动启动服务器)和(或)用于终止执行并保留对话记录。当用户反馈内置代理在无人值守时执行操作时使用。
agent.session-listrunning:falseagent.session-stop --sessionId=<id>--all=trueRevert to the plain static cover-crop:
本技能不适用的场景
pandastudio project.auto-reframe --id=$PID --clear=true --json
- **This is the right verb (NOT `project.set-focal-point`)** whenever a
landscape source with multiple/alternating speakers is cut to 9:16.
`set-focal-point` sets ONE static point for the whole clip — correct only for
a single, stationary talking-head. For director-cut / multi-person footage,
reach for `auto-reframe`.
- **Async + needs a renderer** (bundled offline detection). It opens a hidden
editor for the pass; the DENSE tracking + audio makes it slower than a plain
edit — allow a couple of minutes for a few-minute source. Skips clips already
matching the canvas aspect (nothing to reframe) → reported under `skipped`.
- **`--zoom`**: omit for the default ADAPTIVE punch-in (each speaker's face
sized to a consistent fraction of frame). Pass a fixed value (e.g. `1.3`) to
force a uniform punch-in on every shot.
- **Set the 9:16 aspect FIRST** (`project.set-aspect-ratio --aspect=9:16`), then
auto-reframe — the track is computed for the canvas aspect and is ignored if
the aspect later changes (recompute after an aspect switch).
- **Preview and export render the crop identically, per frame** — the preview
camera pans live. Verify from the EXPORTED mp4 across a close-up, a pan, AND a
wide two-shot (render-frame is fine too, but the export is the source of truth
near shot cuts).
- Also exposed in the editor as the **"Track speakers"** button (Video → Layout).
- v1 limitation: one subject per shot — a *held* two-shot where two people
banter frames the dominant talker (no mid-shot switching yet).- 云视频API(HeyGen、Runway、Sora)。PandaStudio是纯本地工具。
- 直接编辑项目JSON。该格式由编辑器维护,版本间会发生变化。使用
.pandastudio/project.read,并将JSON视为两次读取之间的黑盒。project.save - 云视频API——PandaStudio是纯本地工具;在用户设备上渲染。
export.start
Publishing (YouTube + Instagram)
参考文件
Hard rules: YouTube defaults to — never public without explicit user say; Instagram needs a Business/Creator account; never publish in the wrong workspace (confirm ). Flows: connect → publish an export. Full detail: .
privacyStatusunlistedisInActiveWorkspacereference/publishing.md- — 所有verb.noun操作的参数 schema 和单行示例。
reference/commands.md - — 多步骤操作指南+长篇动态图形创作指南(SaaS宣传片、倾斜设备镜头、多图像场景、复杂多覆盖层HTML),之前内嵌在本文档中。创作定制
reference/examples.md图形时按需阅读。motion.render-html - — 每个动态图形模板的外观、支持的参数槽和适配的画幅比例。
reference/templates.md - — 美学规范。规则、视觉词汇、缓动字典、标准模板、预检清单。创作任何动态图形前先阅读。这是将输出从“模板填充”提升到“HyperFrames品质”的关键。
reference/motion-philosophy.md - — 3种交付模式操作手册。模式A(9:16纯摄像头)、模式B(9:16录屏+画中画人脸——PandaStudio独有模式)、模式C(16:9 YouTube侧边覆盖层)。人脸编排、字幕安全区域、音频同步协议、帧验证。任何Shorts/YouTube创作任务都需阅读。
reference/video-authoring.md - — 从零开始制作宣传片/全动态图形视频的设计标准(动态图形即为视频内容)。用画面而非文字叙事的映射表、场景多样性原型、模板化幻灯片反模式、以及动态/叙事/多样性质量门槛。任何宣传片/预告片/从零开始的解释性视频都需先阅读。
reference/promo-and-mg-videos.md - — 约30种命名的、可准确定位的动态效果模式+场景过渡+确定性约束(包括
reference/motion-recipes.md/data-duration以秒为单位的规则、GSAP必须加载的规则、动态效果质量门槛)。为特定镜头创作定制动态效果时阅读。data-start - — 白板/手绘风格解释性视频设计系统(纸张+网格画布、通过pathLength/dashoffset实现SVG手绘笔触、从左到右手写文字展示、马克笔调色板+字体、场景语法、
reference/whiteboard-style.md陷阱)。处理“白板动画/手绘/草图/涂鸦”需求以及从零开始的概念解释性视频(科学、流程、隐喻)时阅读。svgOrigin - — 背景模式、定制片段、完整内置模板目录(包括播客布局)。通过
reference/motion-templates.md选择/渲染模板时阅读。motion.generate - — 渲染操作(
reference/custom-html.md/motion.screenshot/render-html)、透明覆盖层+毛玻璃效果、通过jobId添加。创作定制HTML时与motion-philosophy一起阅读。concat - — 缩放(包括跟随光标+
reference/visual-edits.md)、修剪、变速、裁剪/重构图、人脸居中、摄像头+按章节播客布局、基于主播的编辑。任何视觉编辑任务都需阅读。--anchorSourceMs - —
reference/audio-color-music.md、背景音频区域、内置+Lyria生成音乐、LUT颜色预设。audio.clean - — 字幕(开关/风格/字体)、AI生成标题/描述/时间戳、YouTube缩略图。
reference/captions-metadata.md - — 场景过渡+特效覆盖层,包含克制使用规则。
reference/fx-transitions.md - — Replicate语音合成(TTS)和B-roll图像生成(始终对静态图片应用Ken-Burns效果)。
reference/media-generation.md - — 将导出视频转为垂直剪辑:镜头识别、按镜头拆分项目、9:16操作手册、批量处理。
reference/shorts.md - — 连接并发布到YouTube和Instagram,包含隐私/账号/工作区硬规则。
reference/publishing.md - — 文件夹、重命名、转录语言、转录独立文件。",
reference/projects-and-transcription.md
Memory — remember preferences across chats
—
You have a durable, per-workspace memory that persists across every chat. Its
current contents are already injected into your context each session under
"Durable memory (this workspace)" — so honor anything there without being
re-told. Grow it with three verbs:
bash
pandastudio memory.save --note="Channel is WritePanda; energetic tone, fast cuts"
pandastudio memory.save --note="Default caption template: editorial; brand accent #2563EB"
pandastudio memory.list --json # { entries: [{id,text}], count } — get ids
pandastudio memory.forget --query="a1b2c3" # by id, or a text fragmentWhen to save: the user states a STANDING preference or fact worth carrying
forward — brand, default caption/zoom styles, channel name + tone, a recurring
instruction ("always 9:16 for this client", "never add background music").
When NOT to save: one-off requests about the current edit ("trim the first
10s", "make this clip louder") — those aren't memory.
Save proactively when you notice a durable preference, but keep entries concise
(one fact per note) and use when something the user tells you
supersedes an old note. Memory is per-workspace, so an agency's clients never
share it.
memory.forget—
Editorial decisions — what to ask, what to assume, what NEVER to ask
—
Video editing is a creative task with hundreds of small decisions. Asking the user about all of them kills the magic — they came to you because they wanted to type "edit this" and see something happen. Asking about none of them produces wrong-shape output. The rule:
Ask only when the answer is genuinely user-specific AND can't be inferred AND is hard to reverse. Default everything else, narrate what you did, and iterate via preview.
—
The default edit pipeline (vague "edit my video", no specifics)
—
When the user asks to edit / polish / clean up a video without naming a
specific operation, this is the intended end-to-end pipeline, in order:
- Transcribe any clip where (
clipStates[i].transcribed === false).transcript.transcribe - Remove filler words + immediate repeats ().
transcript.remove-fillers - Fix transcript spelling / STT errors. Read the transcript and correct
obvious misspellings — especially product, brand, person, and technical
names the speech-to-text got wrong (e.g. "Right Panda" → "WritePanda") —
with (patches the word text in place, keeps timing). Do this BEFORE captions, motion graphics, or title generation — they all derive their text from the transcript, so a typo propagates.
transcript.find-replaceonly rewrites words that already exist. When STT DROPPED a word entirely (the transcript is missing a spoken word), usefind-replaceinstead — anchor it withtranscript.insert-words(or--afterWordIdto add at the very start) from--beforeWordId, and passtranscript.get. It computes plausible timing (fills the gap the drop left, sized to the local speaking rate) and is non-destructive — existing word ids, and any trims/zooms/captions anchored to them, are untouched. It does NOT re-transcribe.--text - Cut bad takes. Run (read-only — it never edits). For each
transcript.find-issues/duplicate-take, the default is to keep the most recent (last, cleaner) take and delete the earlier attempt — feed the candidate'sfalse-start(which point at the discarded attempt) intowordIds. EXCEPTtranscript.delete-wordscandidates — those are REVIEW-class: KEEP them by default. A low-severityseverity: "low"means the restart diverges from the fragment, which is often deliberate parallel structure ("one for transcription, one for outreach"), not a flub — deleting it destroys the sentence. Only delete a low candidate when the surrounding context clearly shows an abandoned take. If a candidate is genuinely ambiguous (the repeat might be intentional emphasis, or you can't tell which take is better), ask the user which take to keep rather than guessing.false-start - Remove silences (, 600ms default — same as the UI Remove Silences button) — after content cleanup so it tightens the final timing. The verb returns
transcript.remove-silences+ the newremovedCount(synchronous — you'll know immediately how many it cut). Every transcript cleanup step (2, 4, 5) ADDS trims and shifts the edited timeline, so always finish cleanup BEFORE placing graphics/zooms/lower-thirds (steps 8–9). If you ever place a region from a transcript word before cleanup is done, passrevisionso it re-anchors when the timeline shifts. (If the user already removed silences in the UI, a fresh--anchorSourceMsshows the newproject.read/trimCount/editedDurationMs— treat that as "silences already done".)totalTrimmedMs - Clean audio () on clips where
audio.clean.audioCleaned === false - Add captions — +
caption.toggle(defaultcaption.set-templateper profile; see the caption styles in "DO BY DEFAULT").bold - Add motion graphics — follow the Motion-graphics Rules + selection
guide: first, vary templates by beat, prefer the featured (premium) templates (
motion_list/paper-panel/ the Vox family), and for camera-only / imported footage lead withvox-side-panelorpaper-paneldesigned segments (not the plainervox-side-panel). On any talking-head (split-panel), open with akind === "camera"TOPIC card in the first 10–30s that names what the video is about (from the speaker's opening lines) — the default hook for talking-heads. For explainer content, author custom animated diagrams / flowcharts / charts when the speaker explains how something works or connects and no template captures it (see "Authored graphics") — don't flatten a real explanation into a bullet list.caption-editorial-emphasis - Add emphasis zooms — punch in on the key beats for a dynamic, edited feel (see "Emphasis zooms" just below).
- (Only when the brief is "make it engaging / cinematic / dynamic / give it
energy", NOT a plain "clean it up") — add scene transitions at the
real section boundaries (, ~1 per major section, one consistent style). See the "Effects (FX) & transitions" section — restraint is the rule: a transition belongs at a section change, never on every cut. Do NOT add FX overlays here — FX is explicit-request-only (see the "NOT part of the default pipeline" list below); "make it engaging" does not authorize adding effects.
project.add-transition - Generate title / description / timestamps, then preview.
Do not skip steps, and report what actually ran. Every step the user
confirmed for the full polish must be an ACTUAL verb call this session. Three
steps are skipped far too often — none of them is optional in a full polish:
- — vocalised pauses (um, uh, uhm, umm, hmm, hm) AND immediate repeated words. Default behavior is the SAFE tier only — the words above are sounds, never lexical, so removing every match is unambiguously correct. Pass
transcript.remove-fillersto additionally remove--aggressive; these are real English words too, so the aggressive mode will sometimes cut legitimate uses ("I like this template" loses "like"). Only opt in when the user explicitly asks for a thorough cleanup AND is willing to skim the result for false positives.like / you know / i mean / sort of / kind of - →
transcript.find-issues— bad takes and repeated phrases.transcript.delete-wordsis read-only; you MUST then actually delete the discardedfind-issues(keep the most recent take). RunningwordIdsand not deleting is the same as doing nothing — the bad take stays in the video. (find-issuesfalse-starts are the exception — REVIEW-class, keep by default; see the transcript verbs table.)severity: "low" - — the single most-skipped step; silence removal is what makes a talking-head edit feel tight.
transcript.remove-silences
After the pass, your summary MUST quote the real result each verb returned
(e.g. "removed 14 fillers, cut 2 bad takes + 3 repeated phrases, removed 67
silences, captions on, 4 graphics, 6 zooms"). Never claim a step happened
unless the verb actually ran and you saw its result — "edited everything" with
bad takes, repeats, or silences still in the cut is a failure the user notices
immediately. If a step legitimately returned 0, say so explicitly rather than
omitting it. Treat the numbered pipeline as a checklist: before reporting done,
confirm each item was run (with its count) or consciously skipped for a reason.
Ask-first — exactly once, for scope. Because this is a large, visible
transformation, when the request is a vague "edit my video", confirm scope in
ONE message before running it: list the pipeline above and ask "Want me to do
the full polish — remove fillers/repeats/silences, fix transcript typos, cut
bad takes (keeping the latest), clean audio, add captions, motion graphics, and
emphasis zooms? Or just some of it?" Combine this with the destination-profile
question if that's also unknown — one message, not two.
- On yes (or if they said "just go" / "do everything" / "use defaults") → run the whole pipeline without further per-step asking, then narrate.
- If they named a specific operation ("just add captions", "only remove silences", "add a lower third") → do exactly that and nothing else.
NOT part of the default pipeline — add ONLY on explicit request:
- Background music. Never add a music track to "edit my video" / "do the full polish". Add it only when the user explicitly asks ("add background music", "put a track under it"). "Full polish" does NOT include music.
- Intro / outro cards. Never open with a title card or append an outro/CTA card unless the user explicitly asks for one ("add an intro", "add an outro"). A plain edit keeps the user's footage as the first and last frame.
- FX overlays (). Never add an FX overlay (film burn, light leak, grain, VHS, embers, film-flash, etc.) on your own — NOT on a plain edit, and NOT even for "make it engaging / cinematic". FX is a deliberate creative choice the user makes; add one only when they explicitly ask for an effect ("add a film burn between these clips", "put some grain on it", "add a light leak"). When they do, follow the restraint rules in the "Effects (FX) & transitions" section. (Transitions are different — those ARE part of the engaging-tier flourish; see step 10.)
project.add-fx
If you think music, an intro/outro, or an effect would help, you may suggest
it in your narration ("want me to add a music bed, an intro card, or a film
burn?") — but do not add it until they say yes.
—
Emphasis zooms — punch in on the key beats
—
A perfectly static frame reads as unedited. Adding pushes on
the moments the speaker emphasizes — the payoff word, a "look at this", a key
number, a name reveal — gives the cut a dynamic, professionally-edited feel,
and is part of the default polish. Find the beats from the transcript
(emphatic phrasing, the point of a sentence, a stated result) and place a
~1.5–2.5s zoom on each. Depth: modest (2–3) for talking-head emphasis; punchier
(3–5) for a UI/detail reveal. For recordings, prefer
cursor-telemetry-driven zooms; for /, place them on verbal
emphasis. Don't over-zoom — aim for a real beat roughly every 10–20s, not a
constant push. (Don't pre-ask about zoom positions; infer and let the user
redirect via preview.)
project.add-zoomkind === "screen"cameraupload—
MUST ASK (3 things, only when context is missing)
—
<HARD-GATE>
Before issuing `project.new`, `project.add-*`, `motion.generate`, or `motion.render-html` calls that depend on user-specific information, you MUST have:
1. Destination profile. This single answer drives aspect ratio, pacing, zoom cadence, caption template, and whether to add lower-thirds. (Background music and intro/outro cards are NOT profile defaults — add them ONLY when the user explicitly asks; see the default edit pipeline.) Try to infer first, then fall back to asking:
Inference rules (in order — first match wins). Everything below the user's
own words comes from ONE you're
running in pre-flight anyway — this costs zero extra calls:
project.read --includeTranscript=false- User mentioned "Shorts" / "TikTok" / "Reels" / "Instagram story" / "vertical" / "phone"? → profile (9:16, punchy)
shorts - User mentioned "LinkedIn" / "client pitch" / "professional" / "corporate"? → profile (16:9 or 1:1, restrained)
linkedin - User mentioned "Loom" / "internal" / "async update" / "for the team" / "quick video"? → profile (minimal editing)
loom - User mentioned "YouTube" / "long-form" / "tutorial" / "vlog" / "channel"? → profile (16:9, full pipeline)
youtube-long - Project was created by ? →
project.fork-from-shot(it exists to BE a short)shorts - Project aspect ratio is already 9:16 or 1:1 (project setting, not just source orientation — the user chose that canvas)? →
shorts - Workspace project defaults name a destination ()? → use it
workspace.get-project-defaults - Duration bands (total source duration):
· ≤ 90s → — regardless of orientation; nobody publishes a 45s "long-form video", and a landscape short gets auto-reframed to 9:16 · > 8 min →
shorts(oryoutube-longif it's a screen recording with cursor telemetry and no user framing — internal walkthroughs look exactly like this) · 90s–8 min → genuinely ambiguous: could be a long-form video OR source to cut a short from. Use content type as tiebreaker (screen recording → youtube-long/loom; phone-framed talking head → shorts), else ASK.loom - Source clip is portrait (height > width), no other signal? →
shorts - Source clip is landscape, no other signal? → (safe default — most common)
youtube-long - Ambiguous or no clips yet? → ASK ONE QUESTION:
"Where is this going — YouTube long-form, Shorts/TikTok/Reels, LinkedIn, or internal/async (Loom-style)?"
When the user says "just go" / "use defaults" / "I don't care" →
unless the duration band says shorts (a 60s clip with "just go" is a short).
youtube-longSpecial case — LONG recording + short-form ask ("make a short from this",
"clip this for TikTok"): that's not one profile, it's the fork-from-shot
pipeline (reference/shorts.md) — discover shots in the long source, fork a
9:16 project per shot, then apply the profile to the fork.
shortsThe profile is the source of truth for every default below. See the Video editing playbook section for the full profile table.
2. Lower-third content. If the user said "add a name plate" / "introduce me" / "add a lower-third" but didn't give the actual text, ASK both fields in one message:
"What's the name and the subtitle (e.g. role / company)?"
Don't fabricate a name. Don't invent a job title. Skip this question entirely for the and profiles — they don't use lower thirds.
shortsloom3. Brand / style direction. If the user named a style ("MrBeast" / "MKBHD" / "Vox" / "Kurzgesagt" / "Veritasium" / "Linear" / "Infinite"), first see whether a bundled template fits and set its colors via (most accept brand / ink / accent colors). Only when no template matches the named look, author custom HTML — translate their palette, typography, and motion vocabulary into a composition built against §1. If they gave no style reference AND there are multiple source clips that suggest a brand context, ASK ONE QUESTION:
slotsreference/motion-philosophy.md"Any brand colors, fonts, or visual references — or default look?"
4. Templated vs. custom for a from-scratch video. When the brief is a video
built FROM SCRATCH where the motion graphics ARE the whole video (promo,
explainer, intro/outro, product teaser, CTA piece — no source clip on the main
track), do NOT silently reach for bundled templates: they read as generic for a
hero/marketing asset. Ask once up front:
"Templated quick version, or fully custom? For a promo I'd default to fully custom scenes — they look bespoke, not templated."
If they don't care, default to fully custom (, scenes
authored from ). Only use templates as the
backbone here if they explicitly choose speed — and say so. (This question does
NOT apply to Mode A — graphics layered over existing footage — where templates
stay the default and you should not ask.)
motion.render-htmlreference/motion-philosophy.md5. Voiceover & music for a from-scratch promo. Narration and a music bed are
first-class capabilities ( — local on-device Kokoro
by default for English, no key; cloud Replicate TTS via for more
voices/languages; uses the user's OWN ElevenLabs
account (their CLONED voices — pass the voice name or voice_id; needs the
ElevenLabs key in Settings → Integrations); see reference/media-generation.md;
/ bundled ). For a Mode-B piece (promo,
explainer, intro/outro, teaser), if the brief doesn't already specify, ASK up
front whether to add them:
media.generate-narration--model--model=elevenlabs-directmedia.generate-musicasset.list-music"Want a voiceover and/or a music bed? Both shape the timing, so I'll lock them in before building the scenes."
ALWAYS pass when placing a VOICEOVER with
. Audio overlays are never transcribed otherwise —
only walks main-track clips — so a narration-driven
video ends up with NO transcript, which means no captions and no way for you
to read back what was said. The flag transcribes the narration and merges its
words into the project transcript at the overlay's position. It returns a
; on it before calling or any
transcript verb. Do NOT pass it for music or ambience.
--transcribe=trueproject.add-audiotranscript.transcribetranscribeJobIdjob.waitcaption.togglebash
NARR=$(pandastudio media.generate-narration --text="..." --json | jq -r '.data.path')
OUT=$(pandastudio project.add-audio --id=$ID --audioPath="$NARR" --startMs=0 \
--transcribe=true --json)
pandastudio job.wait --id=$(echo "$OUT" | jq -r '.data.transcribeJobId') --json
pandastudio caption.toggle --id=$ID --enabled=true # now has words to renderThey materially change the build — if there's narration, generate the VO
FIRST and time each scene to its line length (TTS runs longer than you'd
guess; visuals-first forces a re-time pass). See "Audio: decide voiceover &
music FIRST" in .
Don't silently ship a silent promo when audio was available.
reference/promo-and-mg-videos.mdIf these are clear (or already specified), proceed without asking. Combine multiple asks into a single message when possible.
</HARD-GATE>
—
DO BY DEFAULT, narrate transparently
—
For these operations, run them without asking and tell the user what you did in the same message. Every one is reversible (trims are spans, cleaned audio is a sibling file, generated text is just text — nothing is destructive).
Before running or , always call and inspect the array in the response. Each entry looks like:
transcript.transcribeaudio.cleanproject.readclipStatesjson
{ "clipId": "clip-1", "mediaPath": "...", "durationMs": 62400,
"transcribed": true, "wordCount": 312,
"audioCleaned": false, "kind": "camera" }- → skip
transcribed: truefor that clip — it already has a transcript. Running it again would overwrite any manual word edits the user made in the app.transcript.transcribe - → skip
audioCleaned: truefor that clip — theaudio.cleanalready exists..cleaned.wav - → how the clip was captured:
kind(talking-head — a PandaStudio camera-only recording),"camera"(screen recording, maybe with a webcam PiP), or"screen"(external import). This is the authoritative signal for your visual strategy — use it, don't guess from aspect ratio:"upload"(talking-head) ORkind === "camera"(imported video) → there's no screen to zoom into, so lead with a premium designed segment —kind === "upload"orpaper-panel(vox-side-panel) as the default for explainer beats; it's the highest-leverage way to make static footage look produced (project.add-designed-segmentis the plainer fallback — see the Motion-graphics "Rules" §5).split-panel→ use cursor-telemetry zooms, never clip-transform splits. On v1.28+ recordings this is stamped at capture; older projects don't have it, sokind === "screen"infers it (paired webcam track or cursor telemetry →project.read; managed-dir media →screen; elsecamera) and setsupload.kindInferred: true
When is inferred or absent, NEVER assume . is the costly wrong guess — it suppresses the camera enhancements (designed segments, emphasis zooms) and a talking-head ends up flat. So:
kindscreenscreen- A clip flagged is a hint, not gospel. If it reads
kindInferred: truebut you have any doubt (the footage is a person talking, not a UI; no cursor), treat it asscreen— the default when unsure is alwayscamera, nevercamera.screen - If it genuinely matters and you can't tell, ask the user ("Is this clip a screen recording, or you on camera?") rather than guessing .
screen - Once known, lock it: stamps an authoritative value so no future agent has to re-guess (ideal for pre-v1.28 projects — stamp each clip once).
project.set-clip-kind --clipId=… --kind=camera|screen|upload|podcast= a two-speaker composite (host=mediaPath, guest=webcamPath) recorded via Record Podcast; it's set automatically at ingest and edits as one clip with thepodcastwebcam layout.podcast - (top-level on the
contentIssuesresult, not per-clip) → a count summaryproject.read, present only when something is transcribed. If{ total, duplicateTakes, falseStarts, adjacentRepeats }, runtotal > 0during the polish pass to see the actual candidates.transcript.find-issues
Only pass un-processed clips to each operation. If every clip is already transcribed, go straight to .
transcript.get| Operation | Default behaviour |
|---|---|
| Run only on clips where |
| Default: auto-remove vocalised pauses (um/uh/uhm/umm/hmm/hm) + immediately-repeated words. Trim regions; fully reversible. Pass |
| Run after remove-fillers. Surfaces re-takes ( |
| Run after the content cleanup. Runs the SAME two passes as the UI Remove Silences button and unions them: (1) transcript word-gaps (leading, between-word, trailing) and (2) ffmpeg audio-level |
| Denoise only clips where |
| Default to |
| Generate after the edit pass. Show the user; let them say "regenerate" or "use this exact title" or edit inline. |
| Specific zoom moments | Heuristically pick from the transcript ("you said 'click here' at 12.4s — adding a zoom"). Don't pre-ask. Iterate via preview. |
FX overlays ( | NOT do-by-default. Never add an effect on your own — not on a plain edit, not for "make it engaging". Only when the user explicitly asks for one ("add a film burn", "put grain on it"). When they ask, place it where they said and follow the restraint rules. |
Example of good narration after an edit pass:
"Edited. • Transcribed both clips (136 words). • Removed 14 fillers + 3 repeats. Trim regions are reversible — say 'undo fillers' if you want any back. • Cut 2 bad takes (kept the cleaner second attempt each time). • Removed 67 silences (>600ms), tightening ~48s of dead air. • Cleaned audio with DeepFilter on both clips. • Added a zoom at 12.4s where you said 'click here'. • Captions enabled with the bold template (8 other styles available). • Generated a title: 'How I Built This in 24 Hours' — say if you want a different angle.Opening preview now."
Note how the summary names the actual count for every step that ran —
including silences. That's the bar: a summary line backed by a real verb call
and its returned result, never a vague "cleaned it up".
—
NEVER ASK about
—
These produce friction with no upside. Just decide.
- Whether to remove filler words (it's reversible — do it)
- Whether to remove repeated words (same)
- Whether to clean audio (sibling file, original untouched)
- Specific zoom positions or focus points (infer; user redirects via preview)
- Specific caption colors / font sizes (template defaults are good)
- Whether to generate a title / description / timestamps (cheap, useful)
- Whether to enable captions when user said "add captions" (yes — they said yes)
- Specific lower-third design/colour (use defaults; user can swap)
—
Preview, then export. Never export, then preview.
—
After a meaningful edit pass:
- Call (opens the editor focused on the project — same single-step UX, ~2-3s).
preview.show --id=<uuid> - Tell the user what you did (the narration block above).
- Ask: "Does this look right? Anything to tweak before I export?"
- Only after explicit user confirmation call .
export.start
Export is 30-90s and produces a multi-MB MP4. Wasting an export because you skipped the preview is the worst UX failure in this surface. The editor's preview pane shows every effect / caption / FX / lower-third / motion graphic / zoom region exactly as the export will render them.
—
Output modes
—
- Default: pretty one-line summaries to stdout. Show these to the user.
- : raw
--jsonenvelope. Always use{ ok, data, error }when you intend to parse the response or chain commands — pipe through--json.jq
—
Discovery (the most important habit)
—
Don't memorise the command surface — the registry is the source of truth and it grows. Every PandaStudio launch self-describes:
bash
pandastudio commands --json # full schema with arg hints per commandPattern-match against the user's intent. If you can't find a verb that fits, say so rather than fabricating one.
summary—
Async jobs
—
motion.generateexport.startjobIddatabash
pandastudio job.wait --id="$JOB" --timeoutMs=120000 --jsonTerminal is . Read for the rendered MP4.
job.statussucceeded | failed | canceledresult.outputPath—
Argument shape
—
Flags are either scalars () or JSON (). Anything starting with or is parsed as JSON. Strings stay strings; / / numbers auto-coerce.
--name=value--slots='{"title":"x"}'{[truefalse—
Error model
—
Every response: .
{ ok: boolean, data?: ..., error?: string, details?: ... }- HTTP 4xx/5xx → transport problem. CLI exits ≥ 1 to stderr.
- HTTP 200 + → handler-level error. CLI exits 1 with
ok: falseto stderr. The most useful machine-readable codes:error: <msg>- /
license_required— show the user the license activation flowtrial_expired - — typo; run
unknown commandto recoverpandastudio commands - — project paths must live under the user's recordings dir; never pass arbitrary absolute paths
invalid or out-of-tree project path
—
Composing a real edit
—
HARD INVARIANT — every video the agent creates lives in a project.Whatever the brief — promo, explainer, transcript-driven edit, PDF-to-video, a single motion graphic, B-roll over a recording, a one-shot title card — the editor project is the deliverable, not the raw MP4. Loose files inare intermediates the editor consumes; they are never the agent's hand-off.~/Library/Application Support/pandastudio/recordings/The two valid starting states:
- A project is already open in the editor (
returns a non-null id) → use it. Add your work to that timeline. Save. Preview.project.current- No project is open (
returns null, or the chat opened from the home screen) →project.currentis your FIRST tool call, before any motion render / transcription / b-roll generation. Name the project something the user will recognise ("PandaScribe Promo", "Q4 Recap", "How to install — explainer"). Pick the aspect ratio from the destination profile (16:9 YouTube, 9:16 Shorts, 1:1 LinkedIn square).project.newAfter the work lands in the project,to open it in the editor for the user. THAT is the hand-off. The chat message reports the project name/id, not a file path on disk.preview.show --id=<project-id>Exceptions are vanishingly rare — only when the user explicitly says "just give me the MP4, no project" (e.g. they want to upload it elsewhere right away). When in doubt, default to project.
Flow is always: create or open a project → add things → save (conflict-safe)
→ preview. Every verb's arg schema is discoverable at runtime — call
(MCP) or (CLI), or see
. So this section is the judgment the schema can't give
you: which verb, in what order, and the non-obvious gotchas.
system_list_commandspandastudio commandsreference/commands.md—
Target the right project
—
- — create pre-loaded; clip durations are FFmpeg-probed automatically.
project.new --withMedia='["/a.mp4","/b.mp4"]' - (or
project.duplicate --id) — EXACT copy of a project: every edit (trims, zooms, speed, crops, overlays, captions, spotlights, transcript, aspect ratio) is preserved. The copy gets a fresh id and a--pathname and a new<name> (copy)file; the source is untouched (non-destructive) and media files are shared, not copied. Returns.pandastudio. Use when the user asks to duplicate / copy / clone a project — e.g. to try a variant edit without disturbing the original.{ id, path, name } - → the open editor project (
project.currentor{id,path,name,revision,clipCount}). Use it when the user says "this one" / "what's open" — don't ask for an id.null≠ "no projects exist"; fall back tonull.project.list - → full state. Key fields the schema won't spell out:
project.read --id- clips at — each carries
mainTrack.clips[](the clip's own length); there is no per-clipsourceDurationMson the raw clip. For a normalized per-clip view use the top-leveldurationMs, where each entry hasclipStates[],clipId(= sourceDurationMs),durationMs,kind.transcribed - motion-graphic / transition overlays at .
editor.mediaOverlayRegions[] - audio overlays (voiceover, music) at top-level — NOT under
project.audioOverlays[]. Different array from the visual overlays.editor - top-level read summary fields: ,
aspectRatio(post-trim — use for cadence planning),editedDurationMs,sourceDurationMs,totalTrimmedMs, and per-cliptrimCount(clipStates[].kind/camera/screen, your visual-strategy signal). Passuploadafter the first read.--includeTranscript=false
- clips at
—
Adding things — the gotchas (call discovery for the arg schemas)
—
-
Clips:(
add-clipprepends),--atIndex=0,move-clip,split-clip.remove-clipis permanent — no trash. By default it only removes the project file and KEEPS the original source recording. Passproject.deleteto ALSO delete the original recording file(s) from disk (irreversible — only do this when the user explicitly asks to delete the source footage, not just the project). Returns--deleteRecording=true(count removed).deletedRecordings -
Motion graphics — use, NOT
--fromJob, for render outputs. Pass the render--file(afterjobId); the server resolves the path itself:job.waitbashJOB=$(pandastudio motion.render-html --htmlPath=/tmp/card.html --durationMs=4000 --json | jq -r '.data.jobId') pandastudio job.wait --id="$JOB" --json pandastudio project.add-motion-graphic --id=$ID --fromJob="$JOB" --durationMs=4000is only for external uploads and must be quoted — render outputs live under--file(a space); an unquoted path truncates and silently produces a dead overlay (shows on the timeline, never renders).…/Application Support/… -
Placing on a spoken word in a trimmed timeline: trims make source-time ≠ output-time. From, each word has
transcript.get(source) andstartMs(output;editedStartMs= inside a trim → skip it). Pass the sourcenullas the position andstartMsso the region re-anchors when later cleanup trims shift the timeline.--anchorSourceMs=<same>returns the output time (ortimeline.source-to-edited --sourceMs=Nif trimmed).null -
Mid-video graphic on camera / upload footage: don't cover the host — use(the split-panel beat; see Motion-graphics Rules §5). Screen recordings use
project.add-designed-segmentinstead, never a split. Never place a zoom inside a designed-segment / split window — the camera is already cropped to a half-frame band, so zooming it looks wrong. Zoom the full-frame stretches, split the rest.project.add-zoom -
Zoom depth → scale factor.(1–6) maps to a fixed zoom multiplier. When the user asks for "a 1.5x zoom," translate it with this table — there is no separate scale arg:
depthdepth scale feel 1 1.25× barely-there nudge 2 1.5× soft, modern default (talking-head, tutorials) 3 1.8× clear emphasis (UI clicks, callouts) 4 2.2× strong punch-in 5 3.5× dramatic detail 6 5.0× extreme macro -
Zoom / fx / SFX:(default depth 2 = 1.5× + swoosh SFX;
add-zoomto silence),--soundUrl=none(default mouse-click SFX as of v1.36.0),add-motion-graphic(13 bundled FX overlays — film-burn, light-leak, light-flare, lens-flare-sweep, light-streaks, bokeh-drift, prism-leak, dust-scratches, film-grain, vhs-static, embers, snow-drift, film-flash;add-fxadjusts loop speed, default 1; see the "Effects (FX) & transitions" section for when to reach for each),--speed=0.25–4(retune/clear a placed region's SFX). Arg values: discovery (set-region-sound).asset.list-fx -
Spotlight / blur (v1.50.0):— a focus rect over the video.
add-spotlight --atMs=<ms> --durationMs=<ms> [--kind=spotlight|blur](default) DIMS everything outside the rect (draw the eye to one spot);kind=spotlightBLURS everything inside it (hide an email, username, or other sensitive detail in a screen recording). Rect placement iskind=bluras 0..1 fractions of the video (default a centred half-size box,--x --y --width --height).x=y=0.25 width=height=0.5(px corner radius, default 16),--roundness(px soft edge, default 12). Spotlight:--feathering0..1 (surround darkness, default 0.6). Blur:--maskOpacitypx (default 12). The rect tracks content through zooms.--blurAmountis EDITED time (no anchor arg). Edit or delete after placing (v1.85.0):atMspatches only the fields you pass (move/resize, retime, restyle, or flip spotlight<->blur);update-spotlight --regionId=<id> [--startMs --endMs --kind --x --y --width --height --roundness --feathering --maskOpacity --blurAmount]deletes it. Get ids fromremove-spotlight --regionId=<id>underproject.read.editor.spotlightRegions[].id -
Background blur / removal / studio image + person outline (v3.69.0; outline v3.75.0; studio image v3.104.0):— AI PERSON SEGMENTATION on the camera video for the region's span, exactly like a zoom region on the timeline (draggable, trimmable, source-anchored, rebases on trims/speeds).
add-background-effect --mode=blur|remove|image [--atMs=<ms>] [--durationMs=<ms> | --endMs=<ms>] [--strength=<px>] [--backgroundImage=<studioId|path> --backgroundFit=cover|contain] [--outline --outlineWidth=<px> --outlineColor=<hex> --outlineShadow=<bool>] [--anchorSourceMs=<srcMs>]keeps the speaker sharp and gaussian-blurs everything behind them (video-call style;mode=bluris px sigma at 1080p, default 18).--strengthcuts the background away entirely so the project wallpaper/background shows through behind the person — pair it withmode=removefor a branded backdrop, OR with a background-layer media overlay to put an IMAGE/VIDEO behind the speaker (the Shorts look): add the media withset-wallpaper(or flip an existing overlay withadd-motion-graphic --file=<img/video> --layer=background; in the UI, right-click the overlay → "Send behind video"). The removed- background speaker composites OVER that overlay in preview and export.update-region --regionType=overlay --layer=backgroundis the virtual studio: it removes the real background and composites the speaker over a STUDIO PLATE — the one-call way to put someone with a messy room into a clean studio. Setmode=imageto a bundled studio id:--backgroundImage(soft warm key),warm-creator(cool RGB rim),tech-rgb(soft even),neutral-grey(warm tungsten),podcast-warm(natural daylight),daylight-airy(magenta/teal),gradient-gel(dramatic side) — or an absolute/cinematic-dark/file://image path for a custom background. Defaults todata:.warm-creator(default, fill+crop) or--backgroundFit=cover(fit whole plate). Pick a plate lit like the footage for the most natural composite. No outline by default (it's a real background, not a cutout). Same matte tuning as below applies. Matte boundary tuning:containmanually tightens the person edge — >0 pulls it INWARD (kills a background fringe / cleans a loose cut), <0 pushes it out (−60..60);--matteContract=<px>softens the edge (0..60). Both apply to blur AND remove (they adjust the person matte the outline is built from too).--matteFeather=<px>is a colored keyline + drop shadow that hugs the person — the "VOX magazine cutout" look. It is ON BY DEFAULT for--outline(v3.80.0): a bare cutout reads as unintentional, the keyline makes it look designed, so removal ships the VOX look out of the box (pair with a creammode=removefor the reference look). Passset-wallpaperto remove the background with NO keyline. For--outline=falseit's off unless you passmode=blur.--outlinepx@1080p (default 36),--outlineWidthhex (default--outlineColor),#ffffffbool (default true). Duration defaults to 5000ms. CAMERA / TALKING-HEAD footage only — pointless on screen recordings. Runs on a bundled on-device model (no network); preview and export render it identically. Regions live under--outlineShadow(editor.backgroundEffectRegions[]); retime/restyle with.outline = {enabled,width,color,shadow}, delete withupdate-region --regionType=background-effect [--startMs --endMs --mode --strength --backgroundImage --backgroundFit --outline --outlineWidth --outlineColor --outlineShadow --matteContract --matteFeather]. Also batchable insideremove-region --regionType=background-effectasapply-edit-plan.{op:'add-background-effect',atMs,durationMs,mode, strength?,backgroundImage?,backgroundFit?,outline?,outlineWidth?, outlineColor?,outlineShadow?,matteContract?,matteFeather?} -
See a frame to place it (v1.85.0):composites the preview frame at that edited-time to a PNG and returns
render-frame --atMs=<ms> [--outPath=<png>]. A vision model should{ path, width, height, timeMs, maskRect }the returnedreadto LOCATE on-screen text/UI (e.g. the email to blur), then place a focus region.pathis the video content rect as 0..1 fractions of the image — the SAME space as spotlight/blur x/y/width/height. Convert an image-space box (ix,iy,iw,ih) to region coords:maskRect,x=(ix-maskRect.x)/maskRect.width,y=(iy-maskRect.y)/maskRect.height,width=iw/maskRect.width. Typical privacy-blur flow:height=ih/maskRect.height→ read PNG → locate text →render-framewith converted coords →add-spotlight --kind=bluragain to verify →render-frame. Caveats: existing focus regions are NOT drawn (you see content clearly); the frame reflects any active zoom at that time, so prefer an un-zoomed moment for placement.export.start -
Transitions (v2.98.0):— places a scene-change overlay CENTERED on a cut (the opaque peak masks the join). Pass
add-transition --transitionId=<id> --atMs=<cutMs>= the cut time between two clips (fromatMsclip boundaries).project.readhere is EDITED (output) time, and add-transition has NOatMs— unlike add-zoom / add-motion-graphic. If you only have a source-time value (a transcript word'sanchorSourceMs), convert it first withstartMsand pass the result. Ids (timeline.source-to-edited --sourceMs=N): fade-black, fade-white, flash, light-sweep, film-burn, glitch, scribble.asset.list-transitionsdefaults to 1000 (the hand-drawn--durationMsis authored at 2200ms — passscribbleto keep its scribble-on/clear beats intact). Always prefer--durationMs=2200over this static list — it is the source of truth.asset.list-transitionsTime domains at a glance./atMs/startMsare ALWAYS edited (output) time.endMs(source/raw-recording time) is an extra drift-proofing arg on add-zoom, add-motion-graphic, add-designed-segment, add-lower-third — pass it alongsideanchorSourceMswhenatMscame from a transcript word so the region re-anchors across later trims. Verbs WITHOUT an anchor (add-transition, region edits) need a pre-converted edited time.atMs -
Lower thirds (v2.96.0):— ONE async call that renders the nameplate template (default
project.add-lower-third --name="…" --title="…" --atMs=<ms> [--templateId=lt-*]) AND places it as a transparent overlay. Returnslt-vox-marker;{ jobId }resolves once the region is placed. Passjob.waitwhen atMs comes from a transcript word. The 10--anchorSourceMsdesigns are in the template catalog; they also live in the editor's Lower 3rds tab. (The pre-2.95 CSS designs +lt-*are gone; the verb now drives the motion-template pipeline.)--designType -
Reset:— one atomic call wipes every region + audio overlays + turns captions off (keeps clips, transcript, aspect ratio;
project.clear-editsalso resets LUT/crop/webcam/wallpaper). Use it for "start over"; do NOT loop--full=true(that's for removing ONE region byproject.remove-region+--regionType).--regionId
—
Conflict-safe save
—
The editor autosaves, so two writers (you + the editor, or two agents) overwrite
each other silently unless you pass (from
's ). On conflict you get
— re-read,
re-apply your change, retry. All verbs accept it too.
--expectedRevisionproject.readrevision{ code:"revision_conflict", expected, actual, onDiskProject }project.add-*—
Preview without exporting
—
preview.show --id--atMs--autoplaypreview.seek --atMspreview.hidepreview.listpreview.showproject.open—
Motion graphics
—
PandaStudio ships a curated set of YouTube-creator templates. They are
the primary way to add motion graphics — production-grade, editable, and
faster than authoring HTML. Custom HTML () is the
fallback for briefs no template fits (see the next section).
motion_render_html—
Rules — recommendations, not rigid law (bias toward DOING)
—
These are strong defaults, not handcuffs — use your judgment. The worst outcome
is a near-empty timeline because you were being cautious. A video full of
points should be full of graphics.
- FIRST, every time. Discover the catalog + each template's editable slots at runtime; don't generate from memory or copy a templateId out of an example.
motion_list - Add a graphic on most meaningful beats — don't be shy. Name-drops, claims, numbers, lists, comparisons, tool/product mentions, section changes — each is a candidate. If a 5-minute video has ten clear points, ~ten graphics is reasonable. Under-graphicking is as much a failure as the wrong graphic — when a beat clearly wants a visual, add one.
- Vary every scene — consistency comes from a shared SYSTEM, not a repeated
layout. Hold ONE design system across the video (same palette, type family,
motion vocabulary) so it feels cohesive — but give every scene a distinct
composition. Reusing one layout (or one template) beat after beat with only
the text swapped is the #1 way a video reads as a flat, templated slideshow —
and it is fatal for a from-scratch / promo video where the graphics ARE the
video (see ). The ONLY repetition that belongs is a recurring functional overlay on real footage — e.g. the same lower-third name-plate style each time a person is introduced — which is a consistent repeated ELEMENT, not a repeated SCENE. (This replaces the earlier "repetition is good, reuse freely" guidance, which produced templated, monotonous output.)
reference/promo-and-mg-videos.md - The ONE hard line: never misuse a purpose-specific template. A few
templates mean something — use them only when the content matches:
→ a real number (never a generic title) ·
stat-reveal→ exactly two things contrasted ·comparison→ an actual sequence of steps ·flowchart→ a list of points ·key-takeaways→ introducing a person/channel. Everything else (the title family,yt-lower-third, the parallax reveals) is generic — usable on any beat — but still vary the composition beat to beat; don't lean on one look for the whole video (Rule §3).split-panel - Camera-only / imported footage → lead with a PREMIUM designed segment.
When the clip's is
kind(talking-head) or"camera"(imported), there's no screen to zoom into, so a static frame is what makes it look flat. A designed segment (host one half, a panel the other, via"upload") is the highest-leverage fix and your default workhorse for explainer beats on this footage. Default toproject.add-designed-segmentorpaper-panel— those are the featured, high-production panels that instantly level a video up. Usevox-side-panelonly as a plainer fallback or for variety, NOT as the go-to. Use the panels liberally (alternate side + content). Forsplit-panel, prefer cursor-telemetry zooms; don't split the frame.kind === "screen" - Reach for the FEATURED (premium) templates first. Six templates are
flagged top-tier in (
motion_list) — they're the most produced looks we ship, and on an "engaging / cinematic / level it up" brief you should prefer them over the plainer templates (featured: true, the basic title cards):split-panel- Strong workhorses: ,
paper-panel(side panels) andvox-side-panel(the highlighter headline reveal) — reach for these on explainer beats over real footage, but vary the side, content, and which one you use; don't repeat a single panel across the whole video (Rule §3).vox-marker - Reach for the moment the content gives you the hook: (a real number / metric),
vox-stat(a quote or testimonial),vox-quote(circle/callout a specific subject word). Don't force a purpose template where the content doesn't fit — Rule §4 still holds; a stat card with no number looks worse, not better. But when the hook IS there, take it: these elevate the video far more than a generic title card.vox-annotation
- Strong workhorses:
- Text isn't your only option. When a beat wants a visual — logos for the tools being named, a product screenshot, an animated diagram — author a custom graphic (see "Authored graphics" below). Don't force every beat into a text template.
—
Selection guide (beat → template)
—
"Class" tells you when a template fits: Generic = usable on any beat ·
Purpose = only when the content matches · Semi-generic = usable, has a
natural fit. Across a video, vary your compositions — don't repeat one look
(Rule §3). "Generic" means it has no content prerequisite, NOT "repeat it
every scene."
| What's happening in the video | Reach for | Class |
|---|---|---|
| Open / chapter / section title | | Generic |
| Explainer beat, host on camera/imported footage | | Generic workhorse |
| Hero reveal / intro / "ways to use it" recap | | Semi-generic |
| Introduce a person / channel / "subscribe" | | Purpose |
| A real number / metric / result | | Purpose — numbers only |
| "Here are the N things…" / key points / recap | | Purpose |
| This vs that / before vs after / old vs new | | Purpose |
| A simple linear process / N steps | | Purpose |
| How something WORKS / connects / flows — architecture, pipeline, request lifecycle, hierarchy, branching, a loop (richer than a flat step list) | Author a custom animated diagram — see "Authored graphics" below | Authored — the explainer workhorse |
| A trend / chart / data viz (bars, a line, a metric building over time) | Author a custom chart — see "Authored graphics" | Authored |
| A CONCEPT that needs to be DRAWN — from-scratch explainer of something invisible/abstract (science, process, metaphor), or the user says "whiteboard / hand-drawn / sketch / doodle" | Whiteboard hand-drawn style — load | Authored — named Mode-B design system |
| Talking-head OPENER — name the topic in the first 10–30s (default on every camera edit) | | Default for |
| ONE thesis sentence / pull-quote / "money line" (hook or climax) | | Purpose — at most 2–3 per video |
| Logos / tools / partners · a screenshot (a VISUAL, not text) | Author a custom graphic — see "Authored graphics" below | Authored (not a UI template) |
—
Authored graphics — your repertoire is bigger than the gallery
—
The 13 templates above are the UI gallery: fixed-structure, slot-fill. But
your repertoire is larger — you can also author content-specific graphics
that can't be slot-templated because what they show depends on what's being
discussed. This is a first-class capability, not a last resort — when a beat
needs a visual that no template captures, authoring one is the right move, not
a fallback you apologize for. Build these as transparent overlays with
; they composite over the host exactly like an overlay
template.
motion.render-htmlExplainer videos are the prime case. The moment the speaker explains how
something works, connects, or flows — an architecture, a pipeline, a request
lifecycle, a hierarchy, a before→after, a trend over time — a custom animated
diagram, flowchart, or chart communicates it far better than a bullet list or
a title card. The built-in template handles a simple linear sequence
of steps; anything richer you author yourself. Don't flatten a real
explanation into text because the gallery has no template for it — draw it.
flowchart- Animated diagram / flowchart — a data-driven SVG that builds as the speaker talks: boxes + connecting arrows that draw on in sequence, a branching tree, a request flowing through services, a layered architecture stack, a cyclic loop. For ANY "here's how it works / how the pieces fit" explainer beat that's more than a flat list of steps. Reveal each node/edge in time with the narration so the diagram assembles, not just appears.
- Chart / data viz — bars growing, a line plotting, a metric counting, a
donut filling — when the point is a trend or a structural comparison, not a
single headline number (use /
stat-revealfor one number).vox-stat - Logo / brand-card row — N rounded white cards, each a logo, popped in over the lower third. For "we use X, Y, Z", tool / partner / integration mentions (e.g. HeyGen · Claude Code · Hyperframes).
- Image / screenshot showcase — real images via (product shots, UI grabs) in a framed or tilted card.
--assets - Icon / emoji concept callout — a glyph + short label punched on a concept.
- Reuse a template's shell, swap text → graphics — take the look of an overlay template (the lower-band card, the side panel, the depth stack) and put logos / images / animated SVG / a diagram where the text would go.
These never appear in the UI gallery (they're not in the manifest) — they're
yours to consider whenever a slot-fill template doesn't capture the beat.
Copy-able recipes (logo-card row, etc.) live in
; the authoring contract (page
shell, deterministic seek, transparent overlays) is in
.
reference/examples.mdreference/motion-philosophy.md—
The workflow
—
bash
undefined—
1. ALWAYS discover first — templates + editable slots (runtime source of truth).
—
pandastudio motion.list --json # MCP: motion_list
—
2. Pick the template that fits THIS beat (see the selection guide), then
—
render it with your own text/colors + a background mode.
—
Replace <TEMPLATE_ID> + slots with the chosen template's — do NOT
—
hardcode one template for every insert.
—
JOB=$(pandastudio motion.generate
--templateId=<TEMPLATE_ID>
--slots='{ ...the chosen template's slots from motion.list... }'
--aspectRatio=16:9
--json | jq -r '.data.jobId') # MCP: motion_generate pandastudio job.wait --id="$JOB" --json
--templateId=<TEMPLATE_ID>
--slots='{ ...the chosen template's slots from motion.list... }'
--aspectRatio=16:9
--json | jq -r '.data.jobId') # MCP: motion_generate pandastudio job.wait --id="$JOB" --json
—
3. Add the rendered clip to the timeline (at the playhead by default).
—
pandastudio project.add-motion-graphic --id="$PROJECT" --fromJob="$JOB" --durationMs=4000
- **Editable everything** — every template's text, colors, list items, and
images are `slots`; pass only the ones you want to change, the rest use
defaults. `motion.list` returns each template's slot keys, types (`string` /
`color` / `list` / `image`), and defaults.
- **Image slots** — a slot of type `image` takes an **absolute file path** to an
image; the renderer stages the file into the render. Pass a project-media path
or a generated image (e.g. from `media.generate-image`). Two templates take an
image: **`image-showcase`** (the dedicated one — a screenshot/photo on a
3D-tilted card; the go-to for highlighting a product page or app screen in a
demo, 16:9 + 9:16) and `vox-side-panel` (a small photo in its specimen card).
Example: `--slots='{"image":"/abs/screenshot.png","headline":"Ship faster.","eyebrow":"SEE IT IN ACTION"}'`
on `image-showcase`.
- **`--fromJob` not `--file`** — pass the render `jobId` to the add tool; it
resolves the path internally (hand-built paths truncate at the space in
"Application Support" and silently fail).
- **Placement** — `add-motion-graphic` drops it at the playhead/end as an
overlay. To re-time, pass `atMs`. Anchor to a transcript word with
`anchorSourceMs` so it survives later transcript edits.
- **Default SFX** — every primitive that places an animated callout on the
timeline now attaches a stinger by default so the agent's output sounds
the way a hand-edited timeline does. Don't pass `soundUrl` to "set the
default" — omit it. Override only when the user asks for a different
sound, or pass `null` (MCP) to make the callout silent.
| Verb | Default | Notes |
|---|---|---|
| `project.add-motion-graphic` | `bundled:sound/mouse-click` | New in v1.36.0; previously silent. Applies to every motion graphic — generated templates, custom MP4/WebM, designed-segment panels. |
| `project.add-designed-segment` | `bundled:sound/mouse-click` | Inherits from add-motion-graphic. |
| `project.add-zoom` | `bundled:sound/swoosh-fast` | Pre-existing. |
| `project.add-lower-third` | `bundled:sound/mouse-click` | v2.96.0 — inherits the motion-graphic default. |
| `project.add-fx` | none | FX overlays often have their own audio; left to the caller. |
Use `asset.list-sounds` to discover other bundled sound ids when swapping.—
Background modes, designed segments, and the template catalog
—
--backgroundreference/motion-templates.md—
Custom motion graphics — HTML authoring
—
When no template fits, author HTML against the HyperFrames contract. Render verbs (//), transparent overlays + frosted glass, add-by-jobId. Read + before authoring; verbs in .
motion.screenshotrender-htmlconcatreference/motion-philosophy.mdreference/motion-recipes.mdreference/custom-html.md🛑 Pass the HTML INLINE — never write a file first.(MCP:motion.render-html) accepts the whole composition as an inlinemotion_render_htmlstring parameter:html. Author the entire HTML in your response and hand it straight to themotion_render_html({ html: "<!doctype html>…", durationMs, aspectRatio })arg. Do NOT try tohtml/save the HTML to a path and passwrite— the in-app PandaStudio agent has nohtmlPath,write, oredittool (that's deliberate), so a "write the file" plan fails with a tool error.bashis only for the CLI path, where a shell already wrote the file. Local assets (images/fonts) ride along via thehtmlPathparam (absolute paths, referenced by basename in the HTML) — you never write them either. If you catch yourself reaching for aassetstool to make a motion graphic, stop: passwriteinline, or use a bundled template (html) instead.motion_generate
—
Effects (FX) & transitions
—
Golden rule: restraint. Scene transitions () and FX overlays (). Full detail: .
project.add-transitionproject.add-fxreference/fx-transitions.md—
Narration (voiceover) + B-roll generation
—
Replicate TTS narration and gpt-image B-roll (always Ken-Burns + vignette a still, never drop a flat photo). Requires the user's Replicate key. Full detail: .
reference/media-generation.md—
Faceless videos — image-driven, voiceover-led
—
A "faceless" video (a.k.a. faceless YouTube / faceless short) is narration
carrying the story over AI-generated IMAGES that DEPICT each beat, with slow
Ken-Burns motion. No face, no camera. This is the format behind history/mystery/
educational channels. The user says "make a faceless video about X", "faceless
YouTube", "faceless short", or picks the home-screen "Faceless short" preset.
🛑 THE ONE RULE THAT MATTERS: a faceless video is IMAGES, not text cards. Each scene MUST be a real image that SHOWS the beat — for "the cyclops", generate a one-eyed giant in a torch-lit cave, NOT a motion-graphic card with the word "Cyclops" on it. The words belong in the voiceover, never on screen. A deck of animated text titles ("Departure", "The Sirens", …) is the classic failure — it's a title slideshow, not a faceless video, and it looks cheap. Motion-graphic templates /text scenes are the WRONG tool here. Reach for them only for an optional title card or a lower-third stat, never as the scene visuals.motion_render_html
The pipeline — repeat per beat, then export:
- Break the topic into beats. One clear VISUAL idea per beat (~8–20s of narration each). A 3–5 min video is ~12–20 beats.
- Write the narration line for the beat (what the voice says).
- Generate the narration → (local Kokoro by default). Keep each call to ONE beat (~40–60 words); Kokoro caps a single call around ~25s, so long scripts get truncated — narrate per beat, not the whole script at once. Grab its
media.generate-narration.durationMs - Generate the IMAGE for the beat → with a vivid, LITERAL visual prompt of the scene (subject, setting, lighting, mood — no on-screen words). For 16:9 generate
media.generate-image; for 9:16 generate3:2. Keep one art style across every image (state it in every prompt, e.g. "cinematic oil-painting, warm dramatic light") so the 12+ images read as ONE film, not random stock.2:3 - Ken-Burns the image to a clip sized to the narration →
(or
media.image-to-video --imagePath=<img> --durationMs=<beat narration + ~400ms> --aspectRatio=9:16 --zoom=in). This is the NATIVE one-call path — FFmpeg pans/zooms the still into an MP4 and returns--zoom=out. AlternatevideoPath/inacross beats so the cut breathes; a still held flat reads as a dead slideshow. (Only reach for the heavieroutKen-Burns shell when you need a bespoke CSS treatment — grain, parallax layers, vignette animation — that plain pan/zoom can't do.)motion_render_html - Add the clip to the main track in order → (append). The images-in-motion ARE the video.
project.add-clip --media=<videoPath> - Lay the narration under it → (beat start = sum of prior beats' durations).
project.add-audio --audioPath=… --startMs=<beat start> - Polish (optional but expected): a quiet music bed (→
asset.list-musicat low volume, e.g. 0.15), burned captions (project.add-audio/ caption template) since faceless viewers often watch muted, and maybe ONE title card at the top.caption.toggle - Export → (16:9 for YouTube, 9:16 for a faceless short).
export.start
Timing rule: each scene's length = its narration length; Ken-Burns the image
over exactly that span so voice and visual stay locked. Match aspect to
destination. If the user has no Replicate key, image generation is unavailable —
say so and offer bundled templates as a (lesser) fallback, or ask them to add
the key (Settings → Integrations).
Per-beat loop (9:16 short):
bash
undefined—
For one beat — repeat, tracking the running start offset for narration.
—
IMG=$(pandastudio media.generate-image --prompt="a one-eyed giant in a torch-lit cave, cinematic oil-painting, warm dramatic light" --aspectRatio=2:3 --json | jq -r '.data.imagePath')
NARR=$(pandastudio media.generate-narration --text="In the cave of the cyclops, Odysseus faced a giant who ate men whole." --voice=am_michael --json)
DUR=$(echo "$NARR" | jq -r '.data.durationMs'); WAV=$(echo "$NARR" | jq -r '.data.audioPath')
CLIP=$(pandastudio media.image-to-video --imagePath="$IMG" --durationMs=$((DUR + 400)) --aspectRatio=9:16 --zoom=in --json | jq -r '.data.videoPath')
pandastudio project.add-clip --id="$PID" --media="$CLIP"
pandastudio project.add-audio --id="$PID" --audioPath="$WAV" --startMs=$OFFSET --endMs=$((OFFSET + DUR)) --volume=1
—
OFFSET += clip duration (the add-clip return / project.read clip durations) for the next beat.
—
Place each beat's narration at that beat's CLIP start (cumulative sum of prior
clip durations), NOT at the raw narration sum — the clips carry the +400ms tail,
so read the clip durations back (`project.read`) or accumulate `DUR + 400` to keep
audio and video locked.—
Avatar (talking-head) videos — HeyGen
—
Generate a talking-head clip from the user's own HeyGen avatar + a script, then drop it on the timeline and edit/caption/export like any other clip. Bring-your-own key + credits: requires the user's HeyGen API key (Settings → Integrations). The HeyGen API is a paid capability, not on every plan, and renders are billed against the user's own HeyGen credits — say so if a call 401s/402s.
HeyGen is a NATIVE PandaStudio integration — use these verbs, never fetch external URLs or ask the user to paste avatar/voice IDs. The moment the user asks for a HeyGen / avatar / talking-head video, your FIRST action is (and ) to discover their options — don't ask them for IDs, look them up. If either returns "No HeyGen API key set", STOP and tell the user to connect HeyGen in Settings → Integrations before continuing; do not ask anything else first.
media.list-avatarsmedia.list-avatar-voicesDiscover, then generate. is ASYNC (HeyGen renders server-side over minutes) — it returns ; poll with a generous timeout, then add the returned MP4 with .
generate-avatar-video{ jobId }job.waitproject.add-clipbash
undefined—
1. Find the avatar (the user's clone) and a voice.
—
AVATAR=$(pandastudio media.list-avatars --json | jq -r '.data.avatars[0].avatarId')
VOICE=$(pandastudio media.list-avatar-voices --json | jq -r '.data.voices[0].voiceId')
—
2. Kick off the render (16:9 YouTube by default; 9:16 for Shorts).
—
JOB=$(pandastudio media.generate-avatar-video
--avatarId="$AVATAR" --voiceId="$VOICE"
--script="Hey everyone, in today's video…"
--aspectRatio=16:9 --resolution=720p
--json | jq -r '.data.jobId')
--avatarId="$AVATAR" --voiceId="$VOICE"
--script="Hey everyone, in today's video…"
--aspectRatio=16:9 --resolution=720p
--json | jq -r '.data.jobId')
—
3. Wait for the server-side render (minutes) — poll with a long timeout.
—
VIDEO=$(pandastudio job.wait --id="$JOB" --timeoutMs=900000 --json | jq -r '.data.job.result.videoPath')
—
4. Add it to the timeline like any recording; edit/caption/export as normal.
—
pandastudio project.add-clip --id="$PROJECT" --path="$VIDEO"
Args: `avatarId`, `voiceId`, `script` (required); `avatarKind` (`avatar` default | `talking_photo`), `aspectRatio` (`16:9` default | `9:16` | `1:1`), `resolution` (`720p` default | `1080p`), `speed` (0.5–1.5), `backgroundColor` (hex), `outputName`. If `job.wait` returns `timedOut: true`, the render is still going — poll again; never treat it as a failure.—
Transcript-based editing — PandaStudio's signature feature
—
The reason humans pick PandaStudio over Premiere is that you edit by deleting words from the transcript, not by scrubbing the timeline. The CLI exposes the same model.
—
The full edit loop
—
bash
undefined—
0. Check which clips still need processing (avoids clobbering in-app edits)
—
STATE=$(pandastudio project.read --id=$ID --json | jq '.data.clipStates')
—
clipStates: [{ clipId, transcribed, wordCount, audioCleaned }, ...]
—
1. Transcribe only clips that don't already have a transcript
—
(if all are transcribed, skip this step entirely)
—
JOB=$(pandastudio transcript.transcribe --id=$ID --json | jq -r '.data.jobId')
pandastudio job.wait --id=$JOB --timeoutMs=300000 --json
—
2. Pull the merged transcript — every word with edited-time start/end
—
pandastudio transcript.get --id=$ID --json | jq '.data.words[0:20]'
—
3a. AUTO: drop every "um" / "uh" / "you know" + immediate repeats
—
pandastudio transcript.remove-fillers --id=$ID --json
—
→ returns { removedCount, fillersRemoved, repeatsRemoved, trimsAdded }
—
3b. Remove silences (default ≥600ms = the UI button; covers leading/trailing/between-word)
—
Omit --thresholdMs to use the 600ms default; only pass it to override.
—
pandastudio transcript.remove-silences --id=$ID --json
—
→ returns { removedCount, totalTrimmedMs }
—
3c. Fix STT errors — NEVER use project.read → JSON mutation → project.save for this.
—
find-replace patches the word text in-place and preserves timing.
—
pandastudio transcript.find-replace --id=$ID --find="RightPanda" --replace="WritePanda" --json
—
→ returns { replacedCount, wordsPatched }
—
Matcher caveats:
—
- Matching is case-insensitive; punctuation is ignored on both sides
—
(--find="graph, crew"
matches "graph crew").
--find="graph, crew"—
- DIGITS are significant when the find phrase contains them:
—
--find="try30"
matches only the merged STT token "try30", never a plain
--find="try30"—
"try". A letters-only find still ignores transcript-side digits
—
(--find="than"
also matches "than60").
--find="than"—
- A multi-word find ALSO matches a single merged STT token: `--find="Wispr
—
Flow"` hits the one token "Wispr Flow" (STT often emits multi-word brand
—
names as one token), and --find="of $499"
hits the merged "of$499".
--find="of $499"—
- A find that exactly equals a token's raw text always matches, so
—
space/digit/punctuation-bearing tokens — even pure numbers like "30%" —
—
are all targetable verbatim.
—
- A multi-word --find
collapses to the FIRST word's slot: that word's text
--find—
becomes --replace
, the other matched words are blanked. The replacement is
--replace—
the literal --replace
string, so include any punctuation you want kept
--replace—
(the original word's trailing comma/period is not auto-preserved).
—
3d. SURGICAL: delete specific words by ID
—
pandastudio transcript.delete-words --id=$ID --wordIds='["clip-1:w-42","clip-1:w-43"]' --json
—
3e. PHRASE search → bulk delete
—
WORDS=$(pandastudio transcript.search --id=$ID --query="this is a test" --json
| jq -c '[.data.matches[].wordIds | .[]]') pandastudio transcript.delete-words --id=$ID --wordIds="$WORDS" --json
| jq -c '[.data.matches[].wordIds | .[]]') pandastudio transcript.delete-words --id=$ID --wordIds="$WORDS" --json
—
3f. RESTORE previously deleted words (undo a delete / filler / repeat removal).
—
Removes the trim region(s) covering those words; silence trims are left
—
untouched. Mirrors the editor's right-click → Restore on struck-through words.
—
pandastudio transcript.restore-words --id=$ID --wordIds='["clip-1:w-42","clip-1:w-43"]' --json
Every deletion translates internally into a **trim region** the export pipeline skips. It's identical to clicking the word in the editor's transcript pane and hitting delete.
**`transcript.get` shows ALL words, including ones you've deleted.** Deleted words become trim regions — they're gone from the audio export — but they still appear in the raw word list. If you need to verify a deletion happened, check `trimsAdded` in the response rather than calling `transcript.get` afterwards and looking for missing words.
**STT coherence with motion graphics**: Fix all transcript errors with `transcript.find-replace` BEFORE calling `motion.generate` or `llm.generate-title`. The local LLM and motion-graphic slot values are derived from the transcript text — a "RightPanda" in the transcript will propagate into the title card if you generate it first.—
Audio cleanup, background audio, music, and color grading
—
audio.cleanproject.set-clip-volumereference/audio-color-music.md—
Visual edits — zooms, trims, speed, crop, layouts
—
Zoom (incl. follow-cursor + ), cut/speed, crop/reframe, face centering, webcam + per-section podcast layouts, speaker-driven editing, export defaults. Full detail: .
--anchorSourceMsreference/visual-edits.md—
Captions, AI metadata, thumbnails
—
Caption toggle/style/font, AI title/description/timestamps (local LLM), and YouTube thumbnail generation. Full detail: .
reference/captions-metadata.md—
Export — produce the final MP4
—
The centerpiece. Routes through the same Tier-3 PixiJS renderer the editor's Export Video button uses (v1.24+ — was a separate Skia native pipeline before that, see release notes for the convergence). When an editor window is already open on the project you're exporting, the agent reuses it. Otherwise the agent spawns a hidden editor window for the duration of the render and closes it after. Async; poll .
job.waitbash
JOB=$(pandastudio export.start --id=$ID --quality=high --json \
| jq -r '.data.jobId')—
Watch progress (server-side block; returns when done or 5min timeout)
—
pandastudio job.wait --id=$JOB --timeoutMs=600000 --json | jq '.data.job'
—
→ status: "succeeded", result: { outputPath, durationMs, width, height, frameRate }
—
Quality presets: `draft` (1280×720), `standard` / `high` (1920×1080), `ultra` (3840×2160). Aspect ratio comes from the project (`set-aspect-ratio`). Output lands in the recordings dir by default; pass `--outputPath=/somewhere/file.mp4` to override.
The export honours **everything** in the project: clips, trims (incl. those from transcript word deletes), speed regions, zooms, captions, FX, lower-thirds with sound, motion graphics, annotations, cleaned audio, wallpaper, padding/shadow/radius/blur. One verb, full pipeline.
**Video overlays (motion graphics) are fully composited in the export** — both opaque MP4 (`motion.generate`, `motion.render-html`) and transparent WebM (`motion.render-html --transparent`) are composited inline by the Tier-3 pipeline. Alpha channels from VP9/WebM sources are preserved exactly. There is nothing extra you need to call — `export.start` handles it automatically once overlays are on the timeline via `project.add-motion-graphic`.—
Video editing playbook — end-to-end recipe (per destination)
—
When the user says "edit this" / "polish this" / "make this ready for <X>" / "YouTube-ready", follow this runbook. It turns a raw recording into a polished, destination-appropriate video using the foundational verbs above. Different destinations (YouTube long-form, Shorts/TikTok, LinkedIn, Loom) need different defaults — the table below is the source of truth.
Philosophy: good video editing is a series of pattern interrupts that match the platform's viewing context. A YouTube long-form viewer has settled in — cuts every 5–8s and a cinematic LUT feel right. A TikTok viewer is scrolling — you have 3 seconds to hook them and every second after needs a visible change. A LinkedIn viewer is at work — an aggressive soundscape is wrong. A Loom viewer doesn't want any editing at all beyond "cut the fluff". Same tools, very different dials.
—
Destination profiles (the source of truth)
—
Resolve the destination first (see HARD-GATE step 1). Then apply every default below from the matching row — don't mix.
| Parameter | | | | |
|---|---|---|---|---|
| Aspect | 16:9 | 9:16 | 16:9 or 1:1 | 16:9 |
| Hook deadline | 10 s | 3 s | 10 s | — (none) |
| Intro / outro card | only if the user asks (then 2–4 s) | only if the user asks | only if the user asks (then 2–3 s) | none |
| Lower thirds | yes, at first mentions | no (too small vertically) | yes | no |
| Zoom cadence | 3–6 / min | 6–12 / min | 1–2 / min | 0–1 / min |
| Default emphasis zoom duration | 7 s | 3 s | 4 s | 2 s |
| Sustained held-zoom duration (section reframe) | 15 s | — | 8 s | — |
| Agent zooms on screen-share clips | NEVER (telemetry handles it) | NEVER | NEVER | NEVER |
| Zoom SFX volume | 1.0 (swoosh-fast) | 1.0 (swoosh-fast) | 0.5 (or | |
| Filler/silence removal | yes | yes | yes | yes (aggressive — minSilenceMs 300) |
| Speed regions (B-roll) | 1.5–2× | 2–3× or cut entirely | 1.25–1.5× | none |
| LUT preset | by content type @ 0.5–0.8 | | | none |
| Background music | only if the user asks (then vol 0.15) | only if the user asks (then vol 0.30) | none | none |
| Captions enabled | no burn-in — keyword pops instead (measured: 0/9 studied long-form videos burn speech captions; see longform-styles.md LF4) | yes (required) | yes | optional |
| Caption template | — (keyword-pop overlays, not caption templates; if the user INSISTS on captions: | | | |
| Export quality | | | | |
LUT by content type (only for — other profiles use their fixed preset above):
youtube-long| Content type | Preset | Intensity |
|---|---|---|
| Tech tutorial / SaaS demo | | 0.7 |
| Cinematic vlog | | 0.9 |
| Educational / neutral | | 0.5 |
| Moody storytelling | | 0.7 |
| Travel / lifestyle | | 0.7 |
—
Creator-style overrides (when the user names a style)
—
When the user says "like Ali Abdaal's videos" / "MKBHD style" /
"MrBeast-style" / etc., start from the matching base profile, then
apply the overrides below. These are on top of the profile defaults,
not instead of them. Unlisted styles → fall back to base profile.
| Style | Base profile | Pacing | LUT | Music | Caption template | Motion-graphic cadence + notes |
|---|---|---|---|---|---|---|
| Ali Abdaal (productivity / book reviews / tutorial long-form) | | 1 visual change every 3–5s; aggressive filler + silence removal | | warm ambient / lofi @ 0.15–0.20 | | Intro title card (3s held) · host lower-third at 0:04–0:09 · 3–4 right-rail concept callouts at emphasis claims · 1 stat-reveal full-frame takeover if the video cites a number · outro card 4–6s hold with "Like & Subscribe" + shimmer on handle |
| MKBHD (tech reviews / product-focused long-form) | | 1 change every 4–6s — contemplative, product breathes on screen | | upbeat tech-review bed @ 0.20 | | Clean intro wordmark (2s) · minimal lower-thirds (1 total, on first product mention) · stat-reveals over product shots use chrome-gradient numbers on dark · outro: product recap card + subscribe |
| MrBeast (stunts / challenges / max-retention) | | 1 change every 2–3s — very fast, shorts-like cadence | | dramatic orchestral bed @ 0.30 | | Big chrome-gradient kinetic-type every ~5s · frequent full-frame stat takeovers with counter tweens · countdown overlays if the video has stakes · outro: "what's next" teaser card, hold full 6s |
| Veritasium / Kurzgesagt-live (science / education long-form) | | 1 change every 5–7s — contemplative, give diagrams time to read | | ambient / orchestral @ 0.12 | | Explanatory diagrams as motion graphics (labeled SVGs with |
| Vox / Johnny Harris (explainer / essay long-form) | | 1 change every 4–6s — narrative-driven | | cinematic bed @ 0.18 | | Chapter cards at every act break (bold chrome-gradient section titles) · map / timeline / chart motion graphics · pull-quote callouts in right rail · outro: credits card + next video teaser |
Rule: an agent authoring any "style X" edit MUST still follow the 11
Laws from . The style overrides change
palette, cadence, and the suggested music/intro/outro — they do NOT let
you ship flat-white text on a flat-black background. Grid + vignette + grain
reference/motion-philosophy.md- chrome gradient are mandatory regardless of named style.
Music + intro/outro stay opt-in even in style mode. The Music and
motion-graphic-cadence columns above describe what the style would include
— but background music and intro/outro cards are still added ONLY when the
user asked for them (see the default edit pipeline). If the user named a style
without mentioning music or an intro/outro, apply the style's palette /
pacing / captions / mid-roll motion graphics, and offer the music bed +
intro/outro rather than adding them unprompted.
—
Anchoring — every transcript-derived region MUST be anchored
—
This rule applies to FIVE region types: zoom, motion-graphic, lower-third,
annotation, and audio-overlay (when used as SFX, not background music).
The problem. Region positions are stored in edited time —
post-trim playback time. When the user (or you) runs ,
, , or ,
new trim regions get added, the edited-time map shifts, and any region whose
/ was authored against the previous edited time drifts off
the moment it was placed on. A "Like and Subscribe" lower third you placed
on the word "subscribe" silently moves 800ms early because there used to be
800ms of "um"s before it that got trimmed.
transcript.remove-fillerstranscript.remove-silencestranscript.delete-wordstranscript.find-replacestartMsendMsThe fix is the argument. When you derive /
from a transcript word's source time, pass that same value as
. The region records its anchor moment in raw recording
time. Every subsequent trim/speed edit auto-rebases the region's edited
positions back onto the anchor — the lower third stays glued to "subscribe"
no matter how much you trim.
--anchorSourceMsatMsstartMs--anchorSourceMsv1.35.0+: every region is auto-anchored on creation, even without. The primitive now back-computes a source-time anchor from the resolved edited--anchorSourceMs(viaatMs) whenever the caller doesn't supply one explicitly. Regions placed by direct atMs survive subsequent silence removal, filler removal, and word deletion just like transcript-anchored ones. Still passeditedToSourcewhen you have a transcript word in hand — it makes the agent's intent self-documenting and avoids a two-step round trip through edited time. But the days of "I placed an overlay, then trimmed silences, now it's playing at the wrong moment" are over for any project saved by v1.35.0 or later. Legacy projects (schemaVersion < 4) are auto-migrated on the first mutation: every anchorless region gets a source anchor back-filled at its current position.--anchorSourceMsanchors are preserved as opt-outs.type: "free"
Verbs that accept anchors (use them ALWAYS when picking from transcript):
| Verb | Anchor args | When required |
|---|---|---|
| | Always when atMs comes from a transcript word |
| | Always when atMs comes from a transcript word |
| | Always when atMs comes from a transcript word |
| | Always when startMs comes from a transcript word |
| | When the overlay is an SFX pinned to a word. NEVER for background music — those should stay free-floating (a fixed slot of the edited timeline, not anchored to content). |
Free-floating is OK — when the user explicitly placed a region by edited
time (e.g. an outro card at "the last 5 seconds of the timeline"), omit the
anchor. The region stays where you put it regardless of subsequent edits.
anchorSourceMs is global source-time (ms from recording start). In
multi-clip projects, sum the preceding clips' to convert
an in-clip offset to global source time before passing as .
Use to verify where a source-time position falls
on the edited timeline.
sourceDurationMs--anchorSourceMstimeline.source-to-editedThe runbook below already orders pacing FIRST, then regions. That's safe
even without anchors — regions land on the post-trim timeline. But: any
mid-flow re-edit ("actually, remove the part about X" after you've placed
motion graphics) drifts unanchored regions silently. Always anchor when the
position came from a transcript word, even if the runbook order is followed.
The cost is one extra arg per call; the benefit is correctness under iteration.
—
The runbook (ordered — do not rearrange)
—
bash
undefined—
0. Resolve the project + destination profile
—
ID=$(pandastudio project.current --json | jq -r '.data.project.id // empty')
[ -z "$ID" ] && ID=$(pandastudio project.list --json | jq -r '.data.projects[0].id')
—
$PROFILE is set from HARD-GATE step 1: youtube-long | shorts | linkedin | loom
—
$ASPECT is derived from the profile:
—
youtube-long | linkedin | loom → 16:9
—
shorts → 9:16
—
pandastudio project.set-aspect-ratio --id=$ID --aspect=$ASPECT
—
1. PACING — the default cleanup pipeline (see "default edit pipeline" in
—
Editorial decisions for the mandatory steps + report-counts rule). Run it
—
in full — none of these is optional. audio.clean fires async; wait on it
—
before export (step 5). First read pulls the transcript (needed for step 2);
—
later reads pass --includeTranscript=false.
—
pandastudio project.read --id=$ID --json
pandastudio transcript.transcribe --id=$ID # skip if transcribed
AUDIO_CLEAN_JOB=$(pandastudio audio.clean --id=$ID --json | jq -r '.data.jobId // empty') # async
pandastudio transcript.remove-fillers --id=$ID # fillers + immediate repeats
—
Bad takes + repeated phrases: find-issues is READ-ONLY — you MUST then delete
—
the discarded wordIds (keep the most recent take). Skipping the delete cuts nothing.
—
ISSUES=$(pandastudio transcript.find-issues --id=$ID --json | jq -c '.data.issues')
DROP=$(echo "$ISSUES" | jq -c '[.[] | select(.type=="duplicate-take" or .type=="adjacent-repeat") | .wordIds[]]')
[ "$DROP" != "[]" ] && pandastudio transcript.delete-words --id=$ID --wordIds="$DROP"
SILENCE_MS=$([ "$PROFILE" = "loom" ] && echo 300 || echo 500)
pandastudio transcript.remove-silences --id=$ID --thresholdMs=$SILENCE_MS # NEVER skip; arg is thresholdMs (500ms = UI default)
—
2. EMPHASIS — zooms (skip for loom
). Cadence comes from the profile table.
loom—
—
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
—
HARD RULE #1 — DO NOT add zooms to screen-share clips.
—
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
—
PandaStudio auto-adds zooms to screen-share clips based on cursor
—
telemetry captured during recording. Adding more on top = stacked
—
zooms on the same moments, visual chaos.
—
—
For each clip returned by project.read:
—
- If clip.webcamPath is present → "both" mode (screen + PiP
—
webcam). Telemetry zooms ALREADY exist on this clip. SKIP.
—
- If the clip is pure camera (no webcamPath AND mediaPath is a
—
camera recording) → safe to add zooms on emphasis.
—
- If screen-only (no webcamPath, mediaPath is screen) → telemetry
—
still exists, zooms are auto-added. SKIP.
—
—
Heuristic: if project.read returns existing zoomRegions for a clip
—
whose webcamPath is set, those are telemetry-based — stay OUT of
—
that clip's zoom space entirely. Only author zooms on pure-camera
—
clips (webcamPath absent, no pre-existing zoom regions).
—
—
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
—
HARD RULE #2 — Zoom duration floor is 6 seconds, NOT 1.5–3 seconds.
—
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
—
Premium YouTube zooms ride through a complete thought or cut. A 1.5s
—
or 3s zoom reads as a twitch. Defaults:
—
- Emphasis punch-in zoom (camera pulls in on a word/claim):
—
durationMs=6000 to 8000 (was 1500 — that was wrong)
—
- Sustained / held zoom (camera pulls in on a subject and
—
stays for the whole sub-topic):
—
durationMs=10000 to 20000
—
- Reveal moment (dramatic, loud SFX): durationMs=6000 minimum
—
Depth 3 (modest) for talking-head emphasis; depth 5 only for
—
dramatic reveal beats. Don't stack multiple zooms inside 5s of each
—
other — leaves no time to settle.
—
—
Shot selection: scan the transcript for punchy claims, specific
—
numbers, opinionated statements ("the best", "this changed
—
everything", "most people don't know"), moments of pivot ("and
—
now", "finally", "but here's the thing"). Aim for the user's
—
eyes to want to lean in. NOT every UI-verb word ("click", "select")
—
— that was the old rule, it's wrong for engagement-style edits.
—
—
CRITICAL: ALWAYS pass --anchorSourceMs when atMs comes from a transcript
—
word. Without it, the zoom drifts off the moment as soon as ANY trim is
—
added — and step 1 always adds trims (remove-fillers, remove-silences).
—
The anchor binds the zoom to the source recording moment so it
—
re-anchors automatically on every trim/speed change.
—
Emphasis punch-in — modest scale, held through the thought
—
pandastudio project.add-zoom --id=$ID --clipId=$CLIP_ID
--atMs=<wordStartMs> --anchorSourceMs=<wordStartMs>
--durationMs=7000 --depth=3
--atMs=<wordStartMs> --anchorSourceMs=<wordStartMs>
--durationMs=7000 --depth=3
—
Sustained held zoom — reframe on a topic and stay for the full section
—
pandastudio project.add-zoom --id=$ID --clipId=$CLIP_ID
--atMs=<sectionStartMs> --anchorSourceMs=<sectionStartMs>
--durationMs=15000 --depth=3
--atMs=<sectionStartMs> --anchorSourceMs=<sectionStartMs>
--durationMs=15000 --depth=3
—
Reveal moment (dramatic, with SFX). Use SPARINGLY — 1-2 per video max.
—
pandastudio project.add-zoom --id=$ID --clipId=$CLIP_ID
--atMs=<ms> --anchorSourceMs=<ms>
--durationMs=6000 --depth=5
--soundUrl=bundled:sound/dramatic-whoosh --soundVolume=0.7
--atMs=<ms> --anchorSourceMs=<ms>
--durationMs=6000 --depth=5
--soundUrl=bundled:sound/dramatic-whoosh --soundVolume=0.7
—
3. POLISH — skip sections by profile:
—
- shorts
: no lower thirds (tight vertical frame)
shorts—
- loom
: skip 3b, 3c entirely
loom—
NOTE: 3a (intro/outro) and 3d (background music) are OPT-IN — run them
—
ONLY when the user explicitly asked for an intro/outro or music. They are
—
NOT part of the default "edit my video" pass for ANY profile.
—
3a. Intro / outro card — ONLY IF THE USER EXPLICITLY ASKED for one
—
("add an intro", "add an outro card", "open with a title"). Do NOT add
—
one on a plain "edit this" request. When asked: fastest is the
—
creator-card
template via motion.generate; author custom HTML
creator-card—
(reference/motion-philosophy.md §7) only for a bespoke intro.
—
if [ "$USER_ASKED_FOR_INTRO" = "1" ]; then
JOB=$(pandastudio motion.render-html
--htmlPath=/tmp/intro-title.html
--durationMs=3000 --json | jq -r '.data.jobId') FILE=$(pandastudio job.wait --id=$JOB --json | jq -r '.data.job.result.outputPath') pandastudio project.add-motion-graphic --id=$ID --file=$FILE --durationMs=3000 --atMs=0 fi
--htmlPath=/tmp/intro-title.html
--durationMs=3000 --json | jq -r '.data.jobId') FILE=$(pandastudio job.wait --id=$JOB --json | jq -r '.data.job.result.outputPath') pandastudio project.add-motion-graphic --id=$ID --file=$FILE --durationMs=3000 --atMs=0 fi
—
3b. Lower third at first mention of a person/product (NOT shorts/loom)
—
One call renders the lt-* nameplate AND places it (async job).
—
if [ "$PROFILE" = "youtube-long" ] || [ "$PROFILE" = "linkedin" ]; then
JOB=$(pandastudio project.add-lower-third --id=$ID
--name="<name>" --title="<role>" --atMs=<ms> --anchorSourceMs=<ms>
--json | jq -r '.data.jobId') pandastudio job.wait --id=$JOB fi
--name="<name>" --title="<role>" --atMs=<ms> --anchorSourceMs=<ms>
--json | jq -r '.data.jobId') pandastudio job.wait --id=$JOB fi
—
3c. LUT (use the profile table. For youtube-long, use content-type sub-table.)
—
Apply to every clip via project.set-clip-lut.
—
Skip entirely for loom
.
loom—
3d. Background music — ONLY IF THE USER EXPLICITLY ASKED for music
—
("add background music", "put a track under it"). Do NOT add music on a
—
plain "edit this" request. When asked, use the profile volume (youtube-long
—
0.15, shorts 0.30) and let the user pick / swap the track.
—
if [ "$USER_ASKED_FOR_MUSIC" = "1" ]; then
VOL=$([ "$PROFILE" = "shorts" ] && echo 0.30 || echo 0.15)
pandastudio project.add-audio --id=$ID
--path=bundled:music/corporate-underscore --volume=$VOL --fadeIn=1000 --fadeOut=2000 fi
--path=bundled:music/corporate-underscore --volume=$VOL --fadeIn=1000 --fadeOut=2000 fi
—
4. ACCESSIBILITY — captions per profile
—
if [ "$PROFILE" != "loom" ]; then
pandastudio caption.toggle --id=$ID --enabled=true
TEMPLATE=$(case "$PROFILE" in
shorts) echo "neon";;
linkedin) echo "minimal";;
youtube-long) echo "bold";;
esac)
pandastudio caption.set-template --id=$ID --templateId=$TEMPLATE
ALL CAPS look (common for shorts): force uppercase on any template.
pandastudio caption.set-style --id=$ID --uppercase=true
(uppercase=false turns OFF a template that ships caps, e.g. editorial)
HIDE captions for part of the video. Captions are ON everywhere once
enabled, so these carve out exceptions — use when subtitles would cover
something on screen (a UI demo, on-screen text, a lower third).
Times are EDITED-timeline ms. Overlapping regions are fine.
pandastudio project.add-caption-region --id=$ID --atMs=12000 --durationMs=5000
Remove one (ids come from project.read → editor.captionRegions[].id):
pandastudio project.remove-caption-region --id=$ID --regionId=caption-hide-1
fi
—
MUTE part of the video's audio. Silences the MAIN voice/screen track over a
—
stretch — background music and SFX overlays keep playing (same as the editor).
—
Times are EDITED-timeline ms; durationMs controls how much is silenced.
—
Use for a cough, a name, or dead air you want silent WITHOUT deleting footage
—
(deleting would also cut the video; mute keeps the picture, drops the sound).
—
Overlapping regions are fine. To fully cut both picture and sound, use a trim.
—
pandastudio project.add-mute-region --id=$ID --atMs=12000 --durationMs=3000
—
Remove one (ids come from project.read → editor.muteRegions[].id):
—
pandastudio project.remove-mute-region --id=$ID --regionId=mute-1
—
4.5. VERIFY FRAMES — MANDATORY. Never export without looking.
—
Run motion.verify-frames on every rendered motion-graphic MP4 AND on
—
a draft pass of the full composition (preview.show, then extract
—
frames at hero timestamps). READ each PNG as a multimodal image and
—
confirm against the motion-philosophy §4 pre-flight checklist:
—
- no cropped faces / text overflow / blank frames
—
- captions on the right word
—
- no MG covers host face (Mode C) or screen zone (Mode B)
—
- chrome-gradient text is actually rendering (not flat white)
—
- grid + vignette + grain visible on every hero beat
—
If any frame fails, iterate and re-verify. Do NOT skip this step
—
even when "it's just a quick edit" — this is what separates
—
ships-it-works-ish from ships-it-looks-good.
—
—
🛑 VERIFICATION IS A SEPARATE, BEST-EFFORT STEP — IT NEVER "FAILS" THE EDIT.
—
Every project.* mutation (add-clip, add-motion-graphic, add-audio, etc.)
—
COMMITS to the .pandastudio file the instant its tool returns ok. The
—
verification pass (render a frame, then READ the PNG as a multimodal image)
—
happens AFTER and is a QUALITY check, not part of the edit. So:
—
• Order matters: make the mutating edit FIRST, confirm ok, THEN verify.
—
The edit is already durable before you ever read a frame.
—
• If the frame-read step fails — model times out on the image, "no output
—
for N minutes", rate-limit, "tool read failed" — the EDIT STILL LANDED.
—
Do NOT report the task as failed. Report: "Done — <edit> is on the
—
timeline. I couldn't finish the visual check (model didn't respond);
—
retry the check or switch models." Presenting a committed edit as a
—
failure because a follow-up vision read hung is the wrong outcome.
—
• Keep the read LIGHT so it doesn't hang the model: verify a SINGLE
—
render-frame (one PNG) at the hero timestamp, NOT a big multi-frame
—
render-sheet. One small image is a cheap vision call; a large contact
—
sheet is the read that most often times the model out.
—
pandastudio preview.show --id=$ID # let the project render a full draft
—
Then verify each generated motion-graphic MP4 at its hero timestamps:
—
pandastudio motion.verify-frames --videoPath=/tmp/motion-intro.mp4 \
—
--timestamps='[0.3,1.0,1.8,2.7]' --json
—
Read every returned frame. If any fail, fix + re-render + re-verify.
—
5. EXPORT — quality per profile. Only run AFTER verify-frames passes.
—
First: block on the audio.clean job that's been running in the
—
background since step 1. By now it's almost certainly done (30-60s
—
vs the ~90s+ the rest of the work took), so this resolves instantly.
—
[ -n "$AUDIO_CLEAN_JOB" ] && pandastudio job.wait --id="$AUDIO_CLEAN_JOB"
QUALITY=$([ "$PROFILE" = "loom" ] && echo "standard" || echo "high")
pandastudio export.start --id=$ID --quality=$QUALITY --json | jq -r '.data.jobId' |
xargs -I {} pandastudio job.wait --id={}
xargs -I {} pandastudio job.wait --id={}
undefined—
Performance — keep wall-clock minimal
—
Motion-graphic renders dominate (each scene ~20–45s); everything else is
rounding error. The levers:
- renders are SERIAL — fire ONE,
motion.render-htmlit, then the next. A single-render mutex serializes them; a second concurrent render returnsjob.wait(parallel renders share GPU / scratch / headless-shell state and wedge). Do NOT fire all renders at once.{ ok:false, error:"RENDER_BUSY" } - Run in the background (capture its jobId right after transcribe;
audio.cleanonly beforejob.wait). This IS safe to overlap with a render — the serial limit is render-to-render only.export.start - Pre-flight HTML with before a full render — a ~2s screenshot at
motion.screenshotbeats a 20–45s wasted render. It inlines GSAP and seeks the paused timeline, so it previews the animated frame (not the static pre-JS DOM).--atMs=<mid-scene> - Re-read sparingly: pass after the first
--includeTranscript=false, and reuse theproject.readeach mutation returns instead of re-reading.{ project }
—
Anti-patterns (do NOT do these — all profiles)
—
- 3 effects on the same moment (zoom + lower-third + motion graphic at same t) — visual noise
- Multiple LUTs per project — pick one from the profile table
- SFX on every cut — cap at 1 meaningful SFX per 15–30s (except , where 1 per 5–10s is fine)
shorts - Speed regions over voice — always for setup / B-roll / scrolling only
- Logo intro >5s (any profile) — retention cliff
- Asking the user which filler words to remove — always-safe op, just do it
- Applying defaults to a
youtube-longproject — wrong aspect, music too quiet, captions too subtle, pacing too slowshorts - Motion graphics in — kills the "this is a quick update" vibe
loom
—
Entry triggers — phrases that start the playbook
—
These phrases all route to the edit runbook above. Don't ask the user
to expand any of them — resolve the profile + style, announce the plan,
execute.
| User says | Resolve to |
|---|---|
| "edit this" / "polish this" / "make it engaging" / "make it ready" | |
| "YouTube-ready" / "make a YouTube video" / "edit for YouTube" | |
| "make it a Short" / "TikTok" / "Reel" / "vertical" / "9:16" | |
| "for LinkedIn" | |
| "Loom" / "internal update" / "just cut the fluff" | |
| "edit like Ali Abdaal" / "Ali Abdaal style" / "tutorial style" / "productivity video" | |
| "MKBHD style" / "tech review style" / "product review" | |
| "MrBeast style" / "high-retention" / "challenge video" / "maximum engagement" | |
| "Veritasium style" / "educational" / "Kurzgesagt vibe" / "explainer" | |
| "Vox style" / "essay" / "narrative" / "Johnny Harris style" | |
If the user doesn't name a style and doesn't specify a destination, the
safe default is with no style override — the most common
case by far.
youtube-long—
Pattern: one-shot execution
—
After entry trigger + profile/style resolution, announce the plan in
one sentence and execute. Load
automatically before the motion-graphics steps. Run the full runbook
including the mandatory frame-verification gate. Do NOT ask the user
to approve individual steps.
reference/motion-philosophy.mdI'll edit this as a YouTube long-form in Ali Abdaal style — aggressive filler + silence removal, 3–5s pacing, 4 right-rail concept callouts at emphasis claims, 1 stat-reveal takeover,LUT at 0.5, warm ambient music at 0.15,modernVibrantcaptions at y=0.85, and a 5s outro CTA card. Motion graphics authored against motion-philosophy (chrome-gradient, grid + vignette + grain). Frame-verify before export. ~5 minutes.bold
I'll edit this as a Short — aggressive pacing (hook in 3s, 6–12 zooms/min),LUT at full intensity,modernVibrantcaptions positioned higher, music at 30%. No intro card or lower thirds — they don't fit the vertical frame. Frame-verify before export. ~2 minutes.neon
I'll edit this as a MrBeast-style YouTube video — 2–3s pacing (very fast),LUT at 0.8, dramatic orchestral bed at 0.30, huge color-codedwarmSunsetcaptions, chrome kinetic-type every 5s, full-frame stat takeovers, 6s outro teaser card. Motion graphics authored against motion-philosophy. Frame-verify before export. ~6 minutes.neon
Don't ask the user to micro-manage step choices. The profile table +
creator overrides + motion-philosophy are the answer. If something
genuinely needs user input (missing brand reference for a named style
that has none obvious, missing subject name for the lower third),
collect ALL such questions in a single message — never one-at-a-time.
—
In-app agent sessions — observe and stop
—
The desktop app embeds its own chat agent. Two verbs (app >= 1.60) let an
external agent see and control those sessions: (id,
title, timestamps; returns when the embedded agent server
is down — it never starts it) and
(or ) to abort execution while keeping the transcript. Use
when the user reports the in-app agent doing something unattended.
agent.session-listrunning:falseagent.session-stop --sessionId=<id>--all=true—
What this skill is NOT for
—
- Cloud video APIs (HeyGen, Runway, Sora). PandaStudio is local-only.
- Direct edits to project JSON. The format is owned by the editor and changes between versions. Use
.pandastudio/project.readand treat the JSON as opaque between reads.project.save - Cloud video APIs — PandaStudio is local-only; renders on the user's machine.
export.start
—
Reference files
—
- — every verb.noun with arg schema and a one-line example.
reference/commands.md - — multi-step recipes + the long-form motion-graphics authoring recipes (SaaS promo, tilted-device shots, multi-image scenes, complex multi-overlay HTML) that used to live inline in this file. Read on demand when authoring a bespoke
reference/examples.mdgraphic.motion.render-html - — what each motion-graphic template looks like, with the slots it accepts and which aspect ratios it supports.
reference/templates.md - — the aesthetic contract. Laws, visual vocabulary, easing dictionary, canonical shell, pre-flight checklist. Load this BEFORE authoring any motion graphic. This is what raises output from "template-filled" to "HyperFrames-quality".
reference/motion-philosophy.md - — 3-mode delivery playbook. Mode A (9:16 camera-only), Mode B (9:16 screen-rec + PiP face — PandaStudio's unique mode), Mode C (16:9 YouTube side-overlay). Face choreography, caption safe zones, audio-sync protocol, frame verification. Load this for any shorts/YouTube authoring task.
reference/video-authoring.md - — the design bar for a from-scratch promo / all-motion-graphics video (the graphics ARE the video). Show-don't-tell map, scene-variety archetypes, the templated-slideshow anti-pattern, and the motion/depiction/variety quality gate. Load this FIRST for any promo / teaser / explainer-from-scratch.
reference/promo-and-mg-videos.md - — menu of ~30 named, seek-safe motion patterns + scene transitions + the determinism guardrails (incl. the
reference/motion-recipes.md/data-durationare-in-SECONDS rule, the GSAP-must-load rule, and the motion-quality gate). Read when authoring custom motion for a specific beat.data-start - — the whiteboard / hand-drawn explainer design system (paper + grid canvas, SVG draw-on strokes via pathLength/dashoffset, left-to-right handwriting reveal, marker palette + fonts, scene grammar, the
reference/whiteboard-style.mdgotcha). Load for "whiteboard animation / hand-drawn / sketch / doodle" briefs and from-scratch concept explainers (science, process, metaphor).svgOrigin - — background modes, designed segments, and the full bundled-template catalog (incl. podcast layouts). Read when picking/rendering a template via
reference/motion-templates.md.motion.generate - — the render verbs (
reference/custom-html.md/motion.screenshot/render-html), transparent overlays + frosted glass, add-by-jobId. Read alongside motion-philosophy when authoring custom HTML.concat - — zooms (incl. follow-cursor +
reference/visual-edits.md), trims, speed, crop/reframe, face centering, webcam + per-section podcast layouts, speaker-driven editing. Read for any visual edit.--anchorSourceMs - —
reference/audio-color-music.md, background-audio regions, bundled + Lyria music, and LUT color presets.audio.clean - — captions (toggle/style/font), AI title/description/timestamps, YouTube thumbnails.
reference/captions-metadata.md - — scene transitions + FX overlays, with the restraint rules.
reference/fx-transitions.md - — Replicate narration (TTS) and B-roll image generation (always Ken-Burns a still).
reference/media-generation.md - — turn an export into vertical clips: discover shots, fork per shot, the 9:16 playbook, batch.
reference/shorts.md - — connect + publish to YouTube and Instagram, with the privacy/account/workspace hard rules.
reference/publishing.md - — folders, rename, transcription languages, transcribing a standalone file.
reference/projects-and-transcription.md
—