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:
  1. pandastudio
    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 with
    command -v pandastudio
    (or just call
    pandastudio system.status --json
    ). If it succeeds, you're on the CLI path; every example in this skill is written for it directly.
  2. MCP server — tools prefixed
    mcp__pandastudio__*
    (in-app PandaStudio agent) or
    mcp__writepanda__*
    (external hosts like Cursor, Claude Desktop). Use only when the CLI is not installed — i.e.
    command -v pandastudio
    returned empty AND one of the MCP prefixes is visible in your tools.
Why 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 like
pandastudio project.add-zoom --id=… --atMs=…
maps 1:1 to the MCP tool
project_add_zoom
with the same args (
{id, atMs, …}
). If you're on MCP fallback, translate the CLI examples below by lowercasing the verb and replacing the dot with an underscore.
Do not search the filesystem for the CLI. Don't run
ls /Applications
,
npm list @writepanda/cli
, etc.
command -v pandastudio
is the only probe you need; if it returns nothing, immediately move to the MCP fallback without further discovery.
Version check. This skill requires
@writepanda/cli
≥ 1.15.0 (or
@writepanda/mcp
≥ 1.15.0). On the MCP path, call
system_status
and read the returned version. On the CLI path, run
pandastudio --version
. If < 1.15.0, tell the user to update (
npx @writepanda/mcp@latest
) and restart their agent host. Commands like
asset.list-music
,
asset.list-luts
, and
project.set-clip-lut
do not exist in older versions.

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):
  1. motion_list
    → see every template, its editable slots, and whether it's an overlay (sits over the video with alpha). It also returns
    registryBlocks
    — ~38 curated standalone compositions (effects, overlays, flowcharts, code snippet, beat-driven cuts) that have no slots; render a block's
    htmlPath
    with
    motion_render_html
    . See
    reference/templates.md
    §"Hyperframes registry blocks".
  2. motion_generate { templateId, slots, background }
    → render it with your own text/colors. Returns a
    jobId
    .
  3. job_wait
    → then
    project_add_motion_graphic { fromJob }
    (or
    project_add_designed_segment
    for
    split-panel
    ).
Everything editable — text, colors, list items, and the background mode (
solid
/
transparent
/
glass
) — is controlled through
slots
+
background
. The full catalog (when to use each, what's editable) is in the "Motion graphics" section below.
Templates-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
    motion_render_html
    , 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. Load
    reference/promo-and-mg-videos.md
    first — 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 in
    reference/motion-philosophy.md
    . One NAMED alternative design system exists for Mode B: the whiteboard / hand-drawn explainer (
    reference/whiteboard-style.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.
For Mode A, author custom HTML (
motion_render_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"; load
reference/motion-philosophy.md
before authoring.

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:
  1. motion_list_storyboards
    → see every storyboard: its id,
    audience
    (youtube | podcaster | course), and its
    params
    (the brief — text + color fields; color fields default to the workspace brand kit).
  2. motion_generate_storyboard { storyboardId, params }
    → the server renders each scene and concatenates them into ONE MP4. Returns a
    jobId
    . Omitted color params fall back to the brand kit (
    workspace_set_brand
    ); omitted required text params fail fast, so read the brief first.
  3. job_wait
    → then
    project_add_motion_graphic { fromJob }
    , same as a single template.
Storyboards are slower than a single template (they render N scenes sequentially), so set a generous
job_wait
timeout. 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 several
motion_generate
calls when one fits the brief — it's one call and on-brand by default.
优质视频编辑是一系列贴合平台观看场景的节奏打断操作。YouTube长视频观众已进入沉浸式状态——每5-8秒一次剪辑、电影级LUT滤镜会很合适;TikTok观众正在快速刷信息流——你只有3秒时间抓住他们的注意力,之后每一秒都需要视觉变化;LinkedIn观众处于工作场景——过于强烈的音效不合适;Loom观众只希望“去掉冗余内容”,不需要额外编辑。工具相同,参数设置却大相径庭。

Quickstart

平台预设(权威参考)

Reminder: examples below are the CLI path (preferred). If
command -v pandastudio
returned empty, mentally translate each verb — e.g.
pandastudio system.status --json
system_status
MCP tool,
pandastudio project.add-zoom --id=… …
project_add_zoom
with the same args.
bash
undefined
首先确定目标平台(参见【编辑决策:该问什么、该假设什么、绝对不能问什么】中的步骤1),然后应用对应行的所有默认设置——不要混合不同平台的设置。
参数
youtube-long
(YouTube长视频)
shorts
(Shorts/TikTok/Reels)
linkedin
(LinkedIn)
loom
(内部/异步沟通)
画幅比例16:99:1616:9 或 1:116: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(或设为
none
none
填充词/静音片段移除开启开启开启开启(激进模式——最小静音时长300ms)
变速片段(B-roll)1.5-2×2-3× 或直接删除1.25-1.5×
LUT滤镜预设根据内容类型选择(强度0.5-0.8)
modernVibrant
(强度1.0)
naturalEnhanced
(强度0.3)
背景音乐仅当用户要求时添加(音量0.15)仅当用户要求时添加(音量0.30)
字幕启用状态不内嵌——改用关键词弹出(数据显示:9个调研的长视频均未内嵌语音字幕;参见longform-styles.md的LF4规则)必须启用启用可选
字幕模板—(使用关键词弹出覆盖层,而非字幕模板;若用户坚持要字幕:用
minimal
neon
+ 垂直位置Y=0.85
minimal
minimal
(若启用)
导出画质
high
high
high
standard
(更快导出)
YouTube长视频的LUT滤镜按内容类型选择(其他平台使用上表中的固定预设):
内容类型滤镜预设强度
技术教程/SaaS演示
modernVibrant
0.7
电影感vlog
cinematicTealOrange
0.9
教育类/中性风格
naturalEnhanced
0.5
氛围感叙事
moodyDark
0.7
旅行/生活方式
warmSunset
0.7

1. Confirm the server is reachable AND the user has a license.

创作者风格覆盖(当用户指定风格时)

MCP equivalent: call
system_status
with no args.

pandastudio system.status --json
当用户说“像Ali Abdaal的视频风格”/“MKBHD风格”/“MrBeast风格”等时,先应用对应基础平台预设,再叠加以下覆盖设置。这些设置是在平台默认之上添加,而非替代。未列出的风格则回归基础平台预设。
风格基础平台预设节奏LUT滤镜音乐字幕模板动态图形节奏及说明
Ali Abdaal(生产力/书评/教程类长视频)
youtube-long
3-5秒一次视觉变化;激进移除填充词和静音片段
modernVibrant
(强度0.5)
温暖氛围/lo-fi风格(音量0.15-0.20)
bold
,垂直位置Y=0.85(避开下三分之一字幕条区域)
片头标题卡片(停留3秒)· 0:04-0:09添加主播下三分之一字幕条 · 重点观点处添加3-4个右侧栏概念标注 · 若视频提及数据,添加1次全屏数据展示 · 片尾4-6秒停留卡片,显示“点赞&订阅”及账号名闪烁效果
MKBHD(科技评测/产品类长视频)
youtube-long
4-6秒一次变化——节奏舒缓,产品画面留足展示时间
modernVibrant
(强度0.6)或
cinematicTealOrange
(强度0.5)
轻快科技评测背景音(音量0.20)
minimal
,垂直位置Y=0.85
简洁片头文字标识(停留2秒)· 仅在首次提及产品时添加简洁下三分之一字幕条 · 产品画面上的数据展示使用深色背景+铬渐变数字 · 片尾:产品回顾卡片+订阅提示
MrBeast(特技/挑战/高留存率视频)
youtube-long
2-3秒一次变化——节奏极快,类似Shorts
warmSunset
(强度0.8,饱和度高、暖色调)
戏剧性管弦乐背景音(音量0.30)
neon
超大字号(字体大小~4.5rem,接近5.0rem上限),按主题配色,垂直位置Y=0.85
每约5秒添加大尺寸铬渐变动态文字 · 频繁全屏数据展示并附带数字动画 · 若视频有悬念,添加倒计时覆盖层 · 片尾:“下期预告”卡片,完整停留6秒
Veritasium / Kurzgesagt实景风格(科学/教育类长视频)
youtube-long
5-7秒一次变化——节奏舒缓,给图表留足阅读时间
naturalEnhanced
(强度0.4)
氛围/管弦乐背景音(音量0.12)
minimal
,垂直位置Y=0.85
动态图形形式的解释性图表(带标注的SVG,使用
power2.inOut
动画曲线,标注延迟0.15秒显示)· 章节分隔使用铬渐变章节标题 · 1-2次核心数据全屏展示并附带数字动画 · 片尾:引用来源卡片+订阅提示
Vox / Johnny Harris(解释性/随笔类长视频)
youtube-long
4-6秒一次变化——叙事驱动
cinematicTealOrange
(强度0.7)
电影感背景音(音量0.18)
minimal
,垂直位置Y=0.85
每个章节开头添加章节卡片(粗体铬渐变章节标题)· 地图/时间线/图表动态图形 · 右侧栏添加引用标注 · 片尾:制作人员名单卡片+下期视频预告

2. Discover what's available — never guess command names.

锚定规则——所有基于字幕的区域必须锚定

MCP equivalent: call
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')
pandastudio job.wait --id="$JOB" --json | jq '.data.job.result'
区域位置存储为编辑后时间——即修剪后的播放时间。当用户(或你)执行
transcript.remove-fillers
transcript.remove-silences
transcript.delete-words
transcript.find-replace
时,会添加新的修剪区域,编辑后时间映射会发生偏移,任何基于之前编辑后时间设置
startMs
/
endMs
的区域都会偏离原本放置的时间点。比如你在“subscribe”这个词的位置添加了“点赞订阅”下三分之一字幕条,但如果之后删除了该词之前的800ms填充词,字幕条会提前800ms播放。

4. Add the rendered clip to the timeline at the playhead / a given time.

解决方案:
--anchorSourceMs
参数

pandastudio 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.
当你从字幕单词的源时间推导
atMs
/
startMs
时,将同一个值作为
--anchorSourceMs
传入。区域会记录其锚定的原始录制时间点,后续任何修剪/变速编辑都会自动将区域的编辑后位置重新锚定到原始时间点——下三分之一字幕条会始终固定在“subscribe”这个词的位置,无论你做了多少修剪。
v1.35.0及以上版本:所有区域在创建时自动锚定,即使未传入
--anchorSourceMs
当调用者未显式提供锚定时,系统会自动从解析后的编辑后
atMs
反向计算源时间锚点(通过
editedToSource
)。直接按
atMs
放置的区域,在后续移除静音、填充词或删除单词时,也会像基于字幕锚定的区域一样保持位置准确。不过当你能获取到字幕单词时,仍建议传入
--anchorSourceMs
——这能让代理的意图更清晰,避免往返转换编辑时间的额外步骤。对于v1.35.0及以后保存的项目,“我添加了覆盖层,然后修剪了静音片段,现在它播放位置不对”的问题已彻底解决。旧项目(schemaVersion < 4)会在首次修改时自动迁移:所有无锚定的区域会根据当前位置自动回填源时间锚点。
type: "free"
锚点会保留作为可选关闭项。

Before any tool call: license check

支持锚定的操作(从字幕选位置时必须使用)

Always run
pandastudio system.status --json
first. Read the
license
block:
FieldWhat it means
licensed: true
Full surface available.
licensed: false
+
trialUsesRemaining > 0
Active trial. Full surface available.
automationGated: true
Trial expired, no license. Only
system.*
and
window.focus
work. Stop and tell the user to activate a license in Settings → License.
If the call fails with a connection error, the CLI auto-launches PandaStudio and waits up to 60 s. If it fails with
invalid or missing bearer token
, the on-disk credentials at
~/.config/pandastudio/{token,port}
rotated mid-flight — wait 2 s and retry once.
操作锚定参数必填场景
project.add-zoom
--anchorSourceMs
,
--anchorSourceEndMs
atMs
来自字幕单词时必须传入
project.add-motion-graphic
--anchorSourceMs
,
--anchorSourceEndMs
atMs
来自字幕单词时必须传入
project.add-lower-third
--anchorSourceMs
,
--anchorSourceEndMs
atMs
来自字幕单词时必须传入
project.add-annotation
--anchorSourceMs
,
--anchorSourceEndMs
startMs
来自字幕单词时必须传入
project.add-audio
--anchorSourceMs
,
--anchorSourceEndMs
当覆盖层是绑定到单词的音效时必须传入。绝对不要用于背景音乐——背景音乐应保持自由浮动(固定在编辑后时间线的某个位置,不锚定到内容)。

Workspaces (v1.19+)

自由浮动场景也可接受

PandaStudio is multi-workspace as of v1.19. Every
project.*
/
export.*
/
motion.*
/
caption.*
/
audio.*
query operates inside the active workspace — the one listed in
workspace.current
. Users (typically agencies) separate clients into their own workspaces so credentials, exports, and YouTube connections never cross-contaminate.
Right after
system.status
, check the workspace context:
bash
pandastudio workspace.list --json | jq '.data | { current: .currentWorkspaceId, count: (.workspaces | length), cap: .limit }'
The returned
limit.max
is:
  • 1
    — Starter plan or Trial
  • 3
    — Creator plan
  • null
    — Team plan (unlimited)
Switching workspaces:
bash
undefined
当用户明确要求按编辑后时间放置区域(比如“在时间线最后5秒添加片尾卡片”),则无需传入锚点。无论后续如何编辑,区域都会保持在你放置的位置。

Get the id of a specific client's workspace

anchorSourceMs
是全局源时间(从录制开始的毫秒数)

WS=$(pandastudio workspace.list --json | jq -r '.data.workspaces[] | select(.name == "ACME Agency — Client A") | .id') pandastudio workspace.switch --id=$WS --json
在多片段项目中,需要将片段内的偏移量加上前面所有片段的
sourceDurationMs
,转换为全局源时间后再传入
--anchorSourceMs
。可使用
timeline.source-to-edited
验证源时间位置对应编辑后时间线的哪个点。

Every 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
undefined

If 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
project.locate
FIRST
, before any read/edit/export/publish:
bash
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 --json
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 # 绝对不要跳过;参数是阈值毫秒数(500ms是UI默认值)

Then re-run any pre-flight that depends on workspace state

2. 强调效果——缩放(loom平台跳过)。节奏来自平台预设表。

(license check, youtube account list, replicate key check)

undefined

Project-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 (
wallpaper
),
captionSettings
, and editor
editorDefaults
(padding, shadow, corner radius, blur). The editor also exposes this as a "Save as default for new projects" button.
bash
undefined

Read 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

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
undefined

Manual: set any subset.

关键:当
atMs
来自字幕单词时,必须传入
--anchorSourceMs
。否则,只要添加任何修剪(步骤1总会添加修剪:移除填充词、移除静音片段),缩放就会偏离原本的时间点。锚点会将缩放绑定到原始录制时间点,使其在每次修剪/变速更改时自动重新锚定。

强调切入——适度缩放,覆盖完整观点

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.rename
, project-look defaults, transcription-language switching (Parakeet/Whisper), and transcribing a standalone file → text/SRT/VTT. Full detail:
reference/projects-and-transcription.md
.
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

Recording the screen yourself (agent-driven, v1.86+)

3. 打磨优化——按平台预设跳过对应部分:

-
shorts
:不添加下三分之一字幕条(垂直画幅空间紧张)

-
loom
:完全跳过3b、3c

注意: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使用
creator-card
模板;仅当需要定制片头时,才参考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

1. (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 (
export.find-shots
), fork the source project per shot (
project.fork-from-shot
), the 9:16 vertical playbook, drift detection, and batch N shorts. Full detail:
reference/shorts.md
. To make a short actually RETAIN — "make it engaging/viral", "edit like Hormozi / Ali Abdaal / a podcast clip" — load
reference/shorts-styles.md
: four evidence-based recipes with the seven retention laws, quantified caption/zoom/overlay parameters, and a render-frame verification pass. Load
reference/shorts-cheatsheet.md
alongside it — exact command shapes plus a hyperframes starter shell, so you never grep schemas or other reference files mid-edit. For
youtube-long
edits that should RETAIN (not just play clean), load
reference/longform-styles.md
— 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.
pandastudio 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,
project.set-shorts-layout
is the one-click layout picker:
bash
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={}
undefined

Camera 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
    完成后再启动下一个。
    系统有单渲染互斥锁,并发启动第二个渲染会返回
    { ok:false, error:"RENDER_BUSY" }
    (并行渲染会共享GPU/临时文件/无头浏览器状态,导致系统卡住)。不要同时启动所有渲染任务。
  • 后台运行
    audio.clean
    (转录后立即获取其jobId;仅在
    export.start
    前执行
    job.wait
    )。这与渲染任务并行是安全的——串行限制仅针对渲染任务之间。
  • 完整渲染前先用
    motion.screenshot
    预检HTML
    ——在
    --atMs=<场景中间>
    位置生成约2秒的截图,比浪费20-45秒进行完整渲染更高效。它会内联GSAP并跳转到暂停的时间线,因此能预览动画帧(而非静态JS加载前的DOM)。
  • 减少重复读取:首次
    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.
project.auto-reframe
is a tracked virtual camera that fixes this — the same approach Opus Clip / Vizard use:
  1. Shot detection (ffmpeg scene cuts) segments the source.
  2. 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.
  3. Audio active-speaker — on multi-person shots it frames whoever is talking (mouth-open × speech-energy), not the biggest face.
  4. 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-long
,无风格覆盖
"做好YouTube发布准备" / "制作YouTube视频" / "为YouTube编辑"
youtube-long
,无风格覆盖
"做成Shorts" / "TikTok风格" / "Reel" / "垂直画幅" / "9:16"
shorts
,无风格覆盖
"用于LinkedIn"
linkedin
,无风格覆盖
"Loom风格" / "内部更新" / "只去掉冗余内容"
loom
,无风格覆盖
"像Ali Abdaal那样编辑" / "Ali Abdaal风格" / "教程风格" / "生产力视频"
youtube-long
+ Ali Abdaal风格覆盖
"MKBHD风格" / "科技评测风格" / "产品评测"
youtube-long
+ MKBHD风格覆盖
"MrBeast风格" / "高留存率" / "挑战视频" / "最大化吸引力"
youtube-long
+ MrBeast风格覆盖
"Veritasium风格" / "教育类" / "Kurzgesagt氛围" / "解释性视频"
youtube-long
+ Veritasium风格覆盖
"Vox风格" / "随笔类" / "叙事风格" / "Johnny Harris风格"
youtube-long
+ Vox风格覆盖
若用户未指定风格且未指定平台,安全默认是
youtube-long
无风格覆盖——这是最常见的场景。

Reframe 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次全屏数据展示,
modernVibrant
LUT滤镜强度0.5,温暖氛围音乐音量0.15,
bold
字幕垂直位置Y=0.85,以及5秒片尾CTA卡片。动态图形遵循motion-philosophy规范(铬渐变、网格+暗角+颗粒效果)。导出前会进行帧验证。耗时约5分钟。
我会将这段视频编辑为Shorts短视频——激进节奏(3秒内抓住注意力,6-12次缩放/分钟),
modernVibrant
LUT滤镜满强度,
neon
字幕位置上移,音乐音量30%。不添加片头卡片或下三分之一字幕条——它们不适合垂直画幅。导出前会进行帧验证。耗时约2分钟。
我会将这段视频编辑为MrBeast风格的YouTube视频——2-3秒一次视觉变化(节奏极快),
warmSunset
LUT滤镜强度0.8,戏剧性管弦乐背景音音量0.30,超大尺寸配色
neon
字幕,每5秒添加铬渐变动态文字,全屏数据展示,6秒片尾预告卡片。动态图形遵循motion-philosophy规范。导出前会进行帧验证。耗时约6分钟。
不要让用户微管理步骤选择。平台预设表+创作者覆盖设置+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)允许外部代理查看和控制这些会话:
agent.session-list
(返回会话ID、标题、时间戳;当内置代理服务器未启动时返回
running:false
——不会主动启动服务器)和
agent.session-stop --sessionId=<id>
(或
--all=true
)用于终止执行并保留对话记录。当用户反馈内置代理在无人值守时执行操作时使用。

Revert 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是纯本地工具。
  • 直接编辑
    .pandastudio
    项目JSON
    。该格式由编辑器维护,版本间会发生变化。使用
    project.read
    /
    project.save
    ,并将JSON视为两次读取之间的黑盒。
  • 云视频API——PandaStudio是纯本地工具;
    export.start
    在用户设备上渲染。

Publishing (YouTube + Instagram)

参考文件

Hard rules: YouTube
privacyStatus
defaults to
unlisted
— never public without explicit user say; Instagram needs a Business/Creator account; never publish in the wrong workspace (confirm
isInActiveWorkspace
). Flows: connect → publish an export. Full detail:
reference/publishing.md
.
  • reference/commands.md
    — 所有verb.noun操作的参数 schema 和单行示例。
  • reference/examples.md
    — 多步骤操作指南+长篇动态图形创作指南(SaaS宣传片、倾斜设备镜头、多图像场景、复杂多覆盖层HTML),之前内嵌在本文档中。创作定制
    motion.render-html
    图形时按需阅读。
  • reference/templates.md
    — 每个动态图形模板的外观、支持的参数槽和适配的画幅比例。
  • reference/motion-philosophy.md
    美学规范。规则、视觉词汇、缓动字典、标准模板、预检清单。创作任何动态图形前先阅读。这是将输出从“模板填充”提升到“HyperFrames品质”的关键。
  • reference/video-authoring.md
    3种交付模式操作手册。模式A(9:16纯摄像头)、模式B(9:16录屏+画中画人脸——PandaStudio独有模式)、模式C(16:9 YouTube侧边覆盖层)。人脸编排、字幕安全区域、音频同步协议、帧验证。任何Shorts/YouTube创作任务都需阅读。
  • reference/promo-and-mg-videos.md
    从零开始制作宣传片/全动态图形视频的设计标准(动态图形即为视频内容)。用画面而非文字叙事的映射表、场景多样性原型、模板化幻灯片反模式、以及动态/叙事/多样性质量门槛。任何宣传片/预告片/从零开始的解释性视频都需先阅读。
  • reference/motion-recipes.md
    — 约30种命名的、可准确定位的动态效果模式+场景过渡+确定性约束(包括
    data-duration
    /
    data-start
    以秒为单位的规则、GSAP必须加载的规则、动态效果质量门槛)。为特定镜头创作定制动态效果时阅读。
  • reference/whiteboard-style.md
    白板/手绘风格解释性视频设计系统(纸张+网格画布、通过pathLength/dashoffset实现SVG手绘笔触、从左到右手写文字展示、马克笔调色板+字体、场景语法、
    svgOrigin
    陷阱)。处理“白板动画/手绘/草图/涂鸦”需求以及从零开始的概念解释性视频(科学、流程、隐喻)时阅读。
  • reference/motion-templates.md
    — 背景模式、定制片段、完整内置模板目录(包括播客布局)。通过
    motion.generate
    选择/渲染模板时阅读。
  • reference/custom-html.md
    — 渲染操作(
    motion.screenshot
    /
    render-html
    /
    concat
    )、透明覆盖层+毛玻璃效果、通过jobId添加。创作定制HTML时与motion-philosophy一起阅读。
  • reference/visual-edits.md
    — 缩放(包括跟随光标+
    --anchorSourceMs
    )、修剪、变速、裁剪/重构图、人脸居中、摄像头+按章节播客布局、基于主播的编辑。任何视觉编辑任务都需阅读。
  • reference/audio-color-music.md
    audio.clean
    、背景音频区域、内置+Lyria生成音乐、LUT颜色预设。
  • reference/captions-metadata.md
    — 字幕(开关/风格/字体)、AI生成标题/描述/时间戳、YouTube缩略图。
  • reference/fx-transitions.md
    — 场景过渡+特效覆盖层,包含克制使用规则。
  • reference/media-generation.md
    — Replicate语音合成(TTS)和B-roll图像生成(始终对静态图片应用Ken-Burns效果)。
  • reference/shorts.md
    — 将导出视频转为垂直剪辑:镜头识别、按镜头拆分项目、9:16操作手册、批量处理。
  • reference/publishing.md
    — 连接并发布到YouTube和Instagram,包含隐私/账号/工作区硬规则。
  • 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 fragment
When 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
memory.forget
when something the user tells you supersedes an old note. Memory is per-workspace, so an agency's clients never share it.

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:
  1. Transcribe any clip where
    clipStates[i].transcribed === false
    (
    transcript.transcribe
    ).
  2. Remove filler words + immediate repeats (
    transcript.remove-fillers
    ).
  3. 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
    transcript.find-replace
    (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.
    find-replace
    only rewrites words that already exist. When STT DROPPED a word entirely (the transcript is missing a spoken word), use
    transcript.insert-words
    instead
    — anchor it with
    --afterWordId
    (or
    --beforeWordId
    to add at the very start) from
    transcript.get
    , and pass
    --text
    . 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.
  4. Cut bad takes. Run
    transcript.find-issues
    (read-only — it never edits). For each
    duplicate-take
    /
    false-start
    , the default is to keep the most recent (last, cleaner) take and delete the earlier attempt — feed the candidate's
    wordIds
    (which point at the discarded attempt) into
    transcript.delete-words
    . EXCEPT
    severity: "low"
    candidates — those are REVIEW-class: KEEP them by default.
    A low-severity
    false-start
    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.
  5. Remove silences (
    transcript.remove-silences
    , 600ms default — same as the UI Remove Silences button) — after content cleanup so it tightens the final timing. The verb returns
    removedCount
    + the new
    revision
    (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, pass
    --anchorSourceMs
    so it re-anchors when the timeline shifts. (If the user already removed silences in the UI, a fresh
    project.read
    shows the new
    trimCount
    /
    editedDurationMs
    /
    totalTrimmedMs
    — treat that as "silences already done".)
  6. Clean audio (
    audio.clean
    ) on clips where
    audioCleaned === false
    .
  7. Add captions
    caption.toggle
    +
    caption.set-template
    (default
    bold
    per profile; see the caption styles in "DO BY DEFAULT").
  8. Add motion graphics — follow the Motion-graphics Rules + selection guide:
    motion_list
    first, vary templates by beat, prefer the featured (premium) templates (
    paper-panel
    /
    vox-side-panel
    / the Vox family), and for camera-only / imported footage lead with
    paper-panel
    or
    vox-side-panel
    designed segments
    (not the plainer
    split-panel
    ). On any talking-head (
    kind === "camera"
    ), open with a
    caption-editorial-emphasis
    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.
  9. Add emphasis zooms — punch in on the key beats for a dynamic, edited feel (see "Emphasis zooms" just below).
  10. (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 (
    project.add-transition
    , ~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.
  11. 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:
  • transcript.remove-fillers
    — 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
    --aggressive
    to additionally remove
    like / you know / i mean / sort of / kind of
    ; 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.
  • transcript.find-issues
    transcript.delete-words
    bad takes and repeated phrases.
    find-issues
    is read-only; you MUST then actually delete the discarded
    wordIds
    (keep the most recent take). Running
    find-issues
    and not deleting is the same as doing nothing — the bad take stays in the video. (
    severity: "low"
    false-starts are the exception — REVIEW-class, keep by default; see the transcript verbs table.)
  • transcript.remove-silences
    — the single most-skipped step; silence removal is what makes a talking-head edit feel tight.
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 (
    project.add-fx
    ).
    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.)
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
project.add-zoom
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
kind === "screen"
recordings, prefer cursor-telemetry-driven zooms; for
camera
/
upload
, 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.)

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
project.read --includeTranscript=false
you're running in pre-flight anyway — this costs zero extra calls:
  • User mentioned "Shorts" / "TikTok" / "Reels" / "Instagram story" / "vertical" / "phone"? →
    shorts
    profile (9:16, punchy)
  • User mentioned "LinkedIn" / "client pitch" / "professional" / "corporate"? →
    linkedin
    profile (16:9 or 1:1, restrained)
  • User mentioned "Loom" / "internal" / "async update" / "for the team" / "quick video"? →
    loom
    profile (minimal editing)
  • User mentioned "YouTube" / "long-form" / "tutorial" / "vlog" / "channel"? →
    youtube-long
    profile (16:9, full pipeline)
  • Project was created by
    project.fork-from-shot
    ? →
    shorts
    (it exists to BE a short)
  • 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 (
    workspace.get-project-defaults
    )? → use it
  • Duration bands (total source duration): · ≤ 90s →
    shorts
    — regardless of orientation; nobody publishes a 45s "long-form video", and a landscape short gets auto-reframed to 9:16 · > 8 min →
    youtube-long
    (or
    loom
    if 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.
  • Source clip is portrait (height > width), no other signal? →
    shorts
  • Source clip is landscape, no other signal? →
    youtube-long
    (safe default — most common)
  • 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" →
youtube-long
unless the duration band says shorts (a 60s clip with "just go" is a short).
Special 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
shorts
profile to the fork.
The 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
shorts
and
loom
profiles — they don't use lower thirds.
3. 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
slots
(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
reference/motion-philosophy.md
§1. If they gave no style reference AND there are multiple source clips that suggest a brand context, ASK ONE QUESTION:
"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 (
motion.render-html
, scenes authored from
reference/motion-philosophy.md
). 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.)
5. Voiceover & music for a from-scratch promo. Narration and a music bed are first-class capabilities (
media.generate-narration
local on-device Kokoro by default for English, no key; cloud Replicate TTS via
--model
for more voices/languages;
--model=elevenlabs-direct
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;
media.generate-music
/ bundled
asset.list-music
). For a Mode-B piece (promo, explainer, intro/outro, teaser), if the brief doesn't already specify, ASK up front whether to add them:
"Want a voiceover and/or a music bed? Both shape the timing, so I'll lock them in before building the scenes."
ALWAYS pass
--transcribe=true
when placing a VOICEOVER with
project.add-audio
.
Audio overlays are never transcribed otherwise —
transcript.transcribe
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
transcribeJobId
;
job.wait
on it before calling
caption.toggle
or any transcript verb. Do NOT pass it for music or ambience.
bash
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 render
They 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
reference/promo-and-mg-videos.md
. Don't silently ship a silent promo when audio was available.
If 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
transcript.transcribe
or
audio.clean
, always call
project.read
and inspect the
clipStates
array in the response.
Each entry looks like:
json
{ "clipId": "clip-1", "mediaPath": "...", "durationMs": 62400,
  "transcribed": true, "wordCount": 312,
  "audioCleaned": false, "kind": "camera" }
  • transcribed: true
    → skip
    transcript.transcribe
    for that clip — it already has a transcript. Running it again would overwrite any manual word edits the user made in the app.
  • audioCleaned: true
    → skip
    audio.clean
    for that clip — the
    .cleaned.wav
    already exists.
  • kind
    → how the clip was captured:
    "camera"
    (talking-head — a PandaStudio camera-only recording),
    "screen"
    (screen recording, maybe with a webcam PiP), or
    "upload"
    (external import). This is the authoritative signal for your visual strategy — use it, don't guess from aspect ratio:
    kind === "camera"
    (talking-head) OR
    kind === "upload"
    (imported video) → there's no screen to zoom into, so lead with a premium designed segment —
    paper-panel
    or
    vox-side-panel
    (
    project.add-designed-segment
    ) as the default for explainer beats; it's the highest-leverage way to make static footage look produced (
    split-panel
    is the plainer fallback — see the Motion-graphics "Rules" §5).
    kind === "screen"
    → use cursor-telemetry zooms, never clip-transform splits. On v1.28+ recordings this is stamped at capture; older projects don't have it, so
    project.read
    infers it (paired webcam track or cursor telemetry →
    screen
    ; managed-dir media →
    camera
    ; else
    upload
    ) and sets
    kindInferred: true
    .
When
kind
is inferred or absent, NEVER assume
screen
.
screen
is the costly wrong guess — it suppresses the camera enhancements (designed segments, emphasis zooms) and a talking-head ends up flat. So:
  • A clip flagged
    kindInferred: true
    is a hint, not gospel. If it reads
    screen
    but you have any doubt (the footage is a person talking, not a UI; no cursor), treat it as
    camera
    — the default when unsure is always
    camera
    , never
    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:
    project.set-clip-kind --clipId=… --kind=camera|screen|upload|podcast
    stamps an authoritative value so no future agent has to re-guess (ideal for pre-v1.28 projects — stamp each clip once).
    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 the
    podcast
    webcam layout.
  • contentIssues
    (top-level on the
    project.read
    result, not per-clip) → a count summary
    { total, duplicateTakes, falseStarts, adjacentRepeats }
    , present only when something is transcribed. If
    total > 0
    , run
    transcript.find-issues
    during the polish pass to see the actual candidates.
Only pass un-processed clips to each operation. If every clip is already transcribed, go straight to
transcript.get
.
OperationDefault behaviour
transcript.transcribe
Run only on clips where
clipStates[i].transcribed === false
. Skip the rest.
transcript.remove-fillers
Default: auto-remove vocalised pauses (um/uh/uhm/umm/hmm/hm) + immediately-repeated words. Trim regions; fully reversible. Pass
aggressive=true
to ALSO catch lexical-word fillers (like, you know, i mean, sort of, kind of) — these can wrongly cut legitimate uses ("I like this template" loses "like"), so opt in only when the user wants a thorough cleanup.
transcript.find-issues
Run after remove-fillers. Surfaces re-takes (
duplicate-take
), abandoned restarts (
false-start
), and stutters (
adjacent-repeat
) as candidates — each with the
wordIds
of the discarded attempt. Read-only — it never edits. Default: keep the most recent (last) take and delete the earlier attempt by feeding the candidate's
wordIds
into
transcript.delete-words
but
severity: "low"
candidates are REVIEW-class: keep them by default
(a low
false-start
= the restart diverges from the fragment, often intentional parallel structure like "one for transcription, one for outreach"). The detector already skips comma-terminated parallel list items and lone stopword "repeats" across pause tokens. Review against context first — if a repeat looks intentional (emphasis) or you can't tell which take is cleaner, ask the user which to keep rather than blind-applying.
transcript.remove-silences
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
silencedetect
on each clip's media — pass 2 catches real dead air the transcript misses when speech-to-text invents phantom words over quiet stretches, which is why this now removes the same sections a manual click would (it previously did pass 1 only and left audio-only silence behind). Default threshold 600ms; don't hand-pick a higher value "to be safe" — that leaves dead air the user expects gone.
audio.clean
Denoise only clips where
clipStates[i].audioCleaned === false
. Writes a sibling
.cleaned.wav
; original audio untouched.
caption.set-template
(when user said "add captions" without naming a style)
Default to
bold
. Static styles:
classic, modern, minimal, spotlight, boxed, neon, colored, editorial
(
editorial
= magazine emphasis: the spoken word renders big + accent while the rest shrinks). Animated, transcript-driven styles (each word animates as it's spoken, identical in preview + export):
kineticSlam
(words slam in),
clipWipe
(wipe reveal per word),
gradientPop
(gradient text, elastic pop),
matrixDecode
(character scramble resolves),
glitchRgb
(RGB chromatic split),
blendDifference
(auto-inverts over any footage). Reach for an animated style for Shorts/TikTok energy; keep
bold
/
editorial
for long-form.
llm.generate-title
/
llm.generate-description
/
llm.generate-timestamps
Generate after the edit pass. Show the user; let them say "regenerate" or "use this exact title" or edit inline.
Specific zoom momentsHeuristically pick from the transcript ("you said 'click here' at 12.4s — adding a zoom"). Don't pre-ask. Iterate via preview.
FX overlays (
project.add-fx
)
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:
  1. Call
    preview.show --id=<uuid>
    (opens the editor focused on the project — same single-step UX, ~2-3s).
  2. Tell the user what you did (the narration block above).
  3. Ask: "Does this look right? Anything to tweak before I export?"
  4. 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.
  • --json
    : raw
    { ok, data, error }
    envelope. Always use
    --json
    when you intend to parse the response or chain commands
    — pipe through
    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 command
Pattern-match
summary
against the user's intent. If you can't find a verb that fits, say so rather than fabricating one.

Async jobs

motion.generate
and any future
export.start
return a
jobId
immediately — the
data
block does not carry the result. Wait server-side:
bash
pandastudio job.wait --id="$JOB" --timeoutMs=120000 --json
Terminal
job.status
is
succeeded | failed | canceled
. Read
result.outputPath
for the rendered MP4.

Argument shape

Flags are either scalars (
--name=value
) or JSON (
--slots='{"title":"x"}'
). Anything starting with
{
or
[
is parsed as JSON. Strings stay strings;
true
/
false
/ numbers auto-coerce.

Error model

Every response:
{ ok: boolean, data?: ..., error?: string, details?: ... }
.
  • HTTP 4xx/5xx → transport problem. CLI exits ≥ 1 to stderr.
  • HTTP 200 +
    ok: false
    → handler-level error. CLI exits 1 with
    error: <msg>
    to stderr. The most useful machine-readable codes:
    • license_required
      /
      trial_expired
      — show the user the license activation flow
    • unknown command
      — typo; run
      pandastudio commands
      to recover
    • invalid or out-of-tree project path
      — project paths must live under the user's recordings dir; never pass arbitrary absolute paths

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 in
~/Library/Application Support/pandastudio/recordings/
are intermediates the editor consumes; they are never the agent's hand-off.
The two valid starting states:
  1. A project is already open in the editor (
    project.current
    returns a non-null id) → use it. Add your work to that timeline. Save. Preview.
  2. No project is open (
    project.current
    returns null, or the chat opened from the home screen) →
    project.new
    is 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).
After the work lands in the project,
preview.show --id=<project-id>
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.
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
system_list_commands
(MCP) or
pandastudio commands
(CLI), or see
reference/commands.md
. So this section is the judgment the schema can't give you: which verb, in what order, and the non-obvious gotchas.

Target the right project

  • project.new --withMedia='["/a.mp4","/b.mp4"]'
    — create pre-loaded; clip durations are FFmpeg-probed automatically.
  • project.duplicate --id
    (or
    --path
    ) — 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
    <name> (copy)
    name and a new
    .pandastudio
    file; the source is untouched (non-destructive) and media files are shared, not copied. Returns
    { id, path, name }
    . Use when the user asks to duplicate / copy / clone a project — e.g. to try a variant edit without disturbing the original.
  • project.current
    → the open editor project (
    {id,path,name,revision,clipCount}
    or
    null
    ). Use it when the user says "this one" / "what's open" — don't ask for an id.
    null
    ≠ "no projects exist"; fall back to
    project.list
    .
  • project.read --id
    → full state. Key fields the schema won't spell out:
    • clips at
      mainTrack.clips[]
      — each carries
      sourceDurationMs
      (the clip's own length); there is no per-clip
      durationMs
      on the raw clip. For a normalized per-clip view use the top-level
      clipStates[]
      , where each entry has
      clipId
      ,
      durationMs
      (= sourceDurationMs),
      kind
      ,
      transcribed
      .
    • motion-graphic / transition overlays at
      editor.mediaOverlayRegions[]
      .
    • audio overlays (voiceover, music) at top-level
      project.audioOverlays[]
      — NOT under
      editor
      . Different array from the visual overlays.
    • top-level read summary fields:
      aspectRatio
      ,
      editedDurationMs
      (post-trim — use for cadence planning),
      sourceDurationMs
      ,
      totalTrimmedMs
      ,
      trimCount
      , and per-clip
      clipStates[].kind
      (
      camera
      /
      screen
      /
      upload
      , your visual-strategy signal). Pass
      --includeTranscript=false
      after the first read.

Adding things — the gotchas (call discovery for the arg schemas)

  • Clips:
    add-clip
    (
    --atIndex=0
    prepends),
    move-clip
    ,
    split-clip
    ,
    remove-clip
    .
    project.delete
    is permanent — no trash.
    By default it only removes the project file and KEEPS the original source recording. Pass
    --deleteRecording=true
    to 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
    deletedRecordings
    (count removed).
  • Motion graphics — use
    --fromJob
    , NOT
    --file
    , for render outputs.
    Pass the render
    jobId
    (after
    job.wait
    ); the server resolves the path itself:
    bash
    JOB=$(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=4000
    --file
    is only for external uploads and must be quoted — render outputs live under
    …/Application Support/…
    (a space); an unquoted path truncates and silently produces a dead overlay (shows on the timeline, never renders).
  • Placing on a spoken word in a trimmed timeline: trims make source-time ≠ output-time. From
    transcript.get
    , each word has
    startMs
    (source) and
    editedStartMs
    (output;
    null
    = inside a trim → skip it). Pass the source
    startMs
    as the position and
    --anchorSourceMs=<same>
    so the region re-anchors when later cleanup trims shift the timeline.
    timeline.source-to-edited --sourceMs=N
    returns the output time (or
    null
    if trimmed).
  • Mid-video graphic on camera / upload footage: don't cover the host — use
    project.add-designed-segment
    (the split-panel beat; see Motion-graphics Rules §5). Screen recordings use
    project.add-zoom
    instead, 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.
  • Zoom depth → scale factor.
    depth
    (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:
    depthscalefeel
    11.25×barely-there nudge
    21.5×soft, modern default (talking-head, tutorials)
    31.8×clear emphasis (UI clicks, callouts)
    42.2×strong punch-in
    53.5×dramatic detail
    65.0×extreme macro
  • Zoom / fx / SFX:
    add-zoom
    (default depth 2 = 1.5× + swoosh SFX;
    --soundUrl=none
    to silence),
    add-motion-graphic
    (default mouse-click SFX as of v1.36.0),
    add-fx
    (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;
    --speed=0.25–4
    adjusts loop speed, default 1; see the "Effects (FX) & transitions" section for when to reach for each),
    set-region-sound
    (retune/clear a placed region's SFX). Arg values: discovery (
    asset.list-fx
    ).
  • Spotlight / blur (v1.50.0):
    add-spotlight --atMs=<ms> --durationMs=<ms> [--kind=spotlight|blur]
    — a focus rect over the video.
    kind=spotlight
    (default) DIMS everything outside the rect (draw the eye to one spot);
    kind=blur
    BLURS everything inside it (hide an email, username, or other sensitive detail in a screen recording). Rect placement is
    --x --y --width --height
    as 0..1 fractions of the video (default a centred half-size box,
    x=y=0.25 width=height=0.5
    ).
    --roundness
    (px corner radius, default 16),
    --feathering
    (px soft edge, default 12). Spotlight:
    --maskOpacity
    0..1 (surround darkness, default 0.6). Blur:
    --blurAmount
    px (default 12). The rect tracks content through zooms.
    atMs
    is EDITED time (no anchor arg). Edit or delete after placing (v1.85.0):
    update-spotlight --regionId=<id> [--startMs --endMs --kind --x --y --width --height --roundness --feathering --maskOpacity --blurAmount]
    patches only the fields you pass (move/resize, retime, restyle, or flip spotlight<->blur);
    remove-spotlight --regionId=<id>
    deletes it. Get ids from
    project.read
    under
    editor.spotlightRegions[].id
    .
  • Background blur / removal / studio image + person outline (v3.69.0; outline v3.75.0; studio image v3.104.0):
    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>]
    — 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).
    mode=blur
    keeps the speaker sharp and gaussian-blurs everything behind them (video-call style;
    --strength
    is px sigma at 1080p, default 18).
    mode=remove
    cuts the background away entirely so the project wallpaper/background shows through behind the person — pair it with
    set-wallpaper
    for a branded backdrop, OR with a background-layer media overlay to put an IMAGE/VIDEO behind the speaker (the Shorts look): add the media with
    add-motion-graphic --file=<img/video> --layer=background
    (or flip an existing overlay with
    update-region --regionType=overlay --layer=background
    ; in the UI, right-click the overlay → "Send behind video"). The removed- background speaker composites OVER that overlay in preview and export.
    mode=image
    is 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. Set
    --backgroundImage
    to a bundled studio id:
    warm-creator
    (soft warm key),
    tech-rgb
    (cool RGB rim),
    neutral-grey
    (soft even),
    podcast-warm
    (warm tungsten),
    daylight-airy
    (natural daylight),
    gradient-gel
    (magenta/teal),
    cinematic-dark
    (dramatic side) — or an absolute/
    file://
    /
    data:
    image path for a custom background. Defaults to
    warm-creator
    .
    --backgroundFit=cover
    (default, fill+crop) or
    contain
    (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:
    --matteContract=<px>
    manually tightens the person edge — >0 pulls it INWARD (kills a background fringe / cleans a loose cut), <0 pushes it out (−60..60);
    --matteFeather=<px>
    softens the edge (0..60). Both apply to blur AND remove (they adjust the person matte the outline is built from too).
    --outline
    is a colored keyline + drop shadow that hugs the person — the "VOX magazine cutout" look. It is ON BY DEFAULT for
    mode=remove
    (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 cream
    set-wallpaper
    for the reference look). Pass
    --outline=false
    to remove the background with NO keyline. For
    mode=blur
    it's off unless you pass
    --outline
    .
    --outlineWidth
    px@1080p (default 36),
    --outlineColor
    hex (default
    #ffffff
    ),
    --outlineShadow
    bool (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
    editor.backgroundEffectRegions[]
    (
    .outline = {enabled,width,color,shadow}
    ); retime/restyle with
    update-region --regionType=background-effect [--startMs --endMs --mode --strength --backgroundImage --backgroundFit --outline --outlineWidth --outlineColor --outlineShadow --matteContract --matteFeather]
    , delete with
    remove-region --regionType=background-effect
    . Also batchable inside
    apply-edit-plan
    as
    {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):
    render-frame --atMs=<ms> [--outPath=<png>]
    composites the preview frame at that edited-time to a PNG and returns
    { path, width, height, timeMs, maskRect }
    . A vision model should
    read
    the returned
    path
    to LOCATE on-screen text/UI (e.g. the email to blur), then place a focus region.
    maskRect
    is 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:
    x=(ix-maskRect.x)/maskRect.width
    ,
    y=(iy-maskRect.y)/maskRect.height
    ,
    width=iw/maskRect.width
    ,
    height=ih/maskRect.height
    . Typical privacy-blur flow:
    render-frame
    → read PNG → locate text →
    add-spotlight --kind=blur
    with converted coords →
    render-frame
    again to verify →
    export.start
    . 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.
  • Transitions (v2.98.0):
    add-transition --transitionId=<id> --atMs=<cutMs>
    — places a scene-change overlay CENTERED on a cut (the opaque peak masks the join). Pass
    atMs
    = the cut time between two clips (from
    project.read
    clip boundaries).
    atMs
    here is EDITED (output) time, and add-transition has NO
    anchorSourceMs
    — unlike add-zoom / add-motion-graphic. If you only have a source-time value (a transcript word's
    startMs
    ), convert it first with
    timeline.source-to-edited --sourceMs=N
    and pass the result. Ids (
    asset.list-transitions
    ): fade-black, fade-white, flash, light-sweep, film-burn, glitch, scribble.
    --durationMs
    defaults to 1000 (the hand-drawn
    scribble
    is authored at 2200ms — pass
    --durationMs=2200
    to keep its scribble-on/clear beats intact). Always prefer
    asset.list-transitions
    over this static list — it is the source of truth.
    Time domains at a glance.
    atMs
    /
    startMs
    /
    endMs
    are ALWAYS edited (output) time.
    anchorSourceMs
    (source/raw-recording time) is an extra drift-proofing arg on add-zoom, add-motion-graphic, add-designed-segment, add-lower-third — pass it alongside
    atMs
    when
    atMs
    came 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.
  • Lower thirds (v2.96.0):
    project.add-lower-third --name="…" --title="…" --atMs=<ms> [--templateId=lt-*]
    — ONE async call that renders the nameplate template (default
    lt-vox-marker
    ) AND places it as a transparent overlay. Returns
    { jobId }
    ;
    job.wait
    resolves once the region is placed. Pass
    --anchorSourceMs
    when atMs comes from a transcript word. The 10
    lt-*
    designs are in the template catalog; they also live in the editor's Lower 3rds tab. (The pre-2.95 CSS designs +
    --designType
    are gone; the verb now drives the motion-template pipeline.)
  • Reset:
    project.clear-edits
    — one atomic call wipes every region + audio overlays + turns captions off (keeps clips, transcript, aspect ratio;
    --full=true
    also resets LUT/crop/webcam/wallpaper). Use it for "start over"; do NOT loop
    project.remove-region
    (that's for removing ONE region by
    --regionType
    +
    --regionId
    ).

Conflict-safe save

The editor autosaves, so two writers (you + the editor, or two agents) overwrite each other silently unless you pass
--expectedRevision
(from
project.read
's
revision
). On conflict you get
{ code:"revision_conflict", expected, actual, onDiskProject }
— re-read, re-apply your change, retry. All
project.add-*
verbs accept it too.

Preview without exporting

preview.show --id
pops the live WYSIWYG overlay (~1–2s boot;
--atMs
,
--autoplay
).
preview.seek --atMs
moves the playhead;
preview.hide
closes;
preview.list
inspects it. Call
preview.show
after every significant edit
so the user sees the change live without leaving the chat. (
project.open
opens the full editor — heavy, for handing off to the user.)

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 (
motion_render_html
) is the fallback for briefs no template fits (see the next section).

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.
  1. motion_list
    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.
  2. 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.
  3. 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
    reference/promo-and-mg-videos.md
    ). 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.)
  4. The ONE hard line: never misuse a purpose-specific template. A few templates mean something — use them only when the content matches:
    stat-reveal
    → a real number (never a generic title) ·
    comparison
    → exactly two things contrasted ·
    flowchart
    → an actual sequence of steps ·
    key-takeaways
    → a list of points ·
    yt-lower-third
    → introducing a person/channel. Everything else (the title family,
    split-panel
    , 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).
  5. Camera-only / imported footage → lead with a PREMIUM designed segment. When the clip's
    kind
    is
    "camera"
    (talking-head) or
    "upload"
    (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
    project.add-designed-segment
    ) is the highest-leverage fix and your default workhorse for explainer beats on this footage. Default to
    paper-panel
    or
    vox-side-panel
    — those are the featured, high-production panels that instantly level a video up. Use
    split-panel
    only as a plainer fallback or for variety, NOT as the go-to. Use the panels liberally (alternate side + content). For
    kind === "screen"
    , prefer cursor-telemetry zooms; don't split the frame.
  6. Reach for the FEATURED (premium) templates first. Six templates are flagged top-tier in
    motion_list
    (
    featured: true
    ) — 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 (
    split-panel
    , the basic title cards):
    • Strong workhorses:
      paper-panel
      ,
      vox-side-panel
      (side panels) and
      vox-marker
      (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).
    • Reach for the moment the content gives you the hook:
      vox-stat
      (a real number / metric),
      vox-quote
      (a quote or testimonial),
      vox-annotation
      (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.
  7. 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 videoReach forClass
Open / chapter / section title
creator-card
,
transitions-3d
,
grain-overlay
,
transitions-destruction
,
caption-parallax-layers
— vary the look across sections
Generic
Explainer beat, host on camera/imported footage
paper-panel
(torn-paper sheet + two-line title) or
vox-side-panel
(graph-paper/specimen look) — the premium designed segments that level the video up.
split-panel
(clean brand panel + bullets) is the plainer fallback. Alternate side, content, and panel across beats.
Generic workhorse
Hero reveal / intro / "ways to use it" recap
parallax-zoom
,
parallax-unzoom
Semi-generic
Introduce a person / channel / "subscribe"
yt-lower-third
Purpose
A real number / metric / result
stat-reveal
Purpose — numbers only
"Here are the N things…" / key points / recap
key-takeaways
Purpose
This vs that / before vs after / old vs new
comparison
Purpose
A simple linear process / N steps
flowchart
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" belowAuthored — 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
reference/whiteboard-style.md
: paper canvas, SVG draw-on strokes, handwriting reveal
Authored — named Mode-B design system
Talking-head OPENER — name the topic in the first 10–30s (default on every camera edit)
caption-editorial-emphasis
Default for
kind === "camera"
ONE thesis sentence / pull-quote / "money line" (hook or climax)
caption-editorial-emphasis
Purpose — at most 2–3 per video
Logos / tools / partners · a screenshot (a VISUAL, not text)Author a custom graphic — see "Authored graphics" belowAuthored (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
motion.render-html
; they composite over the host exactly like an overlay template.
Explainer 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
flowchart
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.
  • 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-reveal
    /
    vox-stat
    for one number).
  • 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
    --assets
    (product shots, UI grabs) in a framed or tilted card.
  • 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
reference/examples.md
; the authoring contract (page shell, deterministic seek, transparent overlays) is in
reference/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

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

--background
modes, the host-on-one-half designed-segment pattern, and the full bundled-template catalog (incl. podcast layouts). Full detail:
reference/motion-templates.md
.

Custom motion graphics — HTML authoring

When no template fits, author HTML against the HyperFrames contract. Render verbs (
motion.screenshot
/
render-html
/
concat
), transparent overlays + frosted glass, add-by-jobId. Read
reference/motion-philosophy.md
+
reference/motion-recipes.md
before authoring; verbs in
reference/custom-html.md
.
🛑 Pass the HTML INLINE — never write a file first.
motion.render-html
(MCP:
motion_render_html
) accepts the whole composition as an inline
html
string parameter
:
motion_render_html({ html: "<!doctype html>…", durationMs, aspectRatio })
. Author the entire HTML in your response and hand it straight to the
html
arg. Do NOT try to
write
/save the HTML to a path and pass
htmlPath
— the in-app PandaStudio agent has no
write
,
edit
, or
bash
tool
(that's deliberate), so a "write the file" plan fails with a tool error.
htmlPath
is only for the CLI path, where a shell already wrote the file. Local assets (images/fonts) ride along via the
assets
param (absolute paths, referenced by basename in the HTML) — you never write them either. If you catch yourself reaching for a
write
tool to make a motion graphic, stop: pass
html
inline, or use a bundled template (
motion_generate
) instead.

Effects (FX) & transitions

Golden rule: restraint. Scene transitions (
project.add-transition
) and FX overlays (
project.add-fx
). Full detail:
reference/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 /
motion_render_html
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.
The pipeline — repeat per beat, then export:
  1. 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.
  2. Write the narration line for the beat (what the voice says).
  3. Generate the narration
    media.generate-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
    durationMs
    .
  4. Generate the IMAGE for the beat
    media.generate-image
    with a vivid, LITERAL visual prompt of the scene (subject, setting, lighting, mood — no on-screen words). For 16:9 generate
    3:2
    ; for 9:16 generate
    2:3
    . 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.
  5. Ken-Burns the image to a clip sized to the narration
    media.image-to-video --imagePath=<img> --durationMs=<beat narration + ~400ms> --aspectRatio=9:16 --zoom=in
    (or
    --zoom=out
    ). This is the NATIVE one-call path — FFmpeg pans/zooms the still into an MP4 and returns
    videoPath
    . Alternate
    in
    /
    out
    across beats so the cut breathes; a still held flat reads as a dead slideshow. (Only reach for the heavier
    motion_render_html
    Ken-Burns shell when you need a bespoke CSS treatment — grain, parallax layers, vignette animation — that plain pan/zoom can't do.)
  6. Add the clip to the main track in order
    project.add-clip --media=<videoPath>
    (append). The images-in-motion ARE the video.
  7. Lay the narration under it
    project.add-audio --audioPath=… --startMs=<beat start>
    (beat start = sum of prior beats' durations).
  8. Polish (optional but expected): a quiet music bed (
    asset.list-music
    project.add-audio
    at low volume, e.g. 0.15), burned captions (
    caption.toggle
    / caption template) since faceless viewers often watch muted, and maybe ONE title card at the top.
  9. Export
    export.start
    (16:9 for YouTube, 9:16 for a faceless short).
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
media.list-avatars
(and
media.list-avatar-voices
) 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.
Discover, then generate.
generate-avatar-video
is ASYNC
(HeyGen renders server-side over minutes) — it returns
{ jobId }
; poll
job.wait
with a generous timeout, then add the returned MP4 with
project.add-clip
.
bash
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')

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").

- DIGITS are significant when the find phrase contains them:

--find="try30"
matches only the merged STT token "try30", never a plain

"try". A letters-only find still ignores transcript-side digits

(
--find="than"
also matches "than60").

- 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".

- 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

becomes
--replace
, the other matched words are blanked. The replacement is

the literal
--replace
string, so include any punctuation you want kept

(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

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.clean
(DeepFilter), background-audio regions, bundled + Lyria-generated music, per-clip volume (
project.set-clip-volume
— balance loudness across clips, 0–2 gain, no shell/WAV workaround needed), and LUT color presets. Full detail:
reference/audio-color-music.md
.

Visual edits — zooms, trims, speed, crop, layouts

Zoom (incl. follow-cursor +
--anchorSourceMs
), cut/speed, crop/reframe, face centering, webcam + per-section podcast layouts, speaker-driven editing, export defaults. Full detail:
reference/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.wait
.
bash
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
youtube-long
shorts
(Shorts/TikTok/Reels)
linkedin
loom
(internal/async)
Aspect16:99:1616:9 or 1:116:9
Hook deadline10 s3 s10 s— (none)
Intro / outro cardonly if the user asks (then 2–4 s)only if the user asksonly if the user asks (then 2–3 s)none
Lower thirdsyes, at first mentionsno (too small vertically)yesno
Zoom cadence3–6 / min6–12 / min1–2 / min0–1 / min
Default emphasis zoom duration7 s3 s4 s2 s
Sustained held-zoom duration (section reframe)15 s8 s
Agent zooms on screen-share clipsNEVER (telemetry handles it)NEVERNEVERNEVER
Zoom SFX volume1.0 (swoosh-fast)1.0 (swoosh-fast)0.5 (or
none
)
none
Filler/silence removalyesyesyesyes (aggressive — minSilenceMs 300)
Speed regions (B-roll)1.5–2×2–3× or cut entirely1.25–1.5×none
LUT presetby content type @ 0.5–0.8
modernVibrant
@ 1.0
naturalEnhanced
@ 0.3
none
Background musiconly if the user asks (then vol 0.15)only if the user asks (then vol 0.30)nonenone
Captions enabledno burn-in — keyword pops instead (measured: 0/9 studied long-form videos burn speech captions; see longform-styles.md LF4)yes (required)yesoptional
Caption template— (keyword-pop overlays, not caption templates; if the user INSISTS on captions:
minimal
)
neon
+ positionY 0.85
minimal
minimal
(if any)
Export quality
high
high
high
standard
(faster)
LUT by content type (only for
youtube-long
— other profiles use their fixed preset above):
Content typePresetIntensity
Tech tutorial / SaaS demo
modernVibrant
0.7
Cinematic vlog
cinematicTealOrange
0.9
Educational / neutral
naturalEnhanced
0.5
Moody storytelling
moodyDark
0.7
Travel / lifestyle
warmSunset
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.
StyleBase profilePacingLUTMusicCaption templateMotion-graphic cadence + notes
Ali Abdaal (productivity / book reviews / tutorial long-form)
youtube-long
1 visual change every 3–5s; aggressive filler + silence removal
modernVibrant
@ 0.5
warm ambient / lofi @ 0.15–0.20
bold
, positionY 0.85 (below lower-third zone)
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)
youtube-long
1 change every 4–6s — contemplative, product breathes on screen
modernVibrant
@ 0.6 OR
cinematicTealOrange
@ 0.5
upbeat tech-review bed @ 0.20
minimal
@ positionY 0.85
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)
youtube-long
1 change every 2–3s — very fast, shorts-like cadence
warmSunset
@ 0.8 (saturated, warm)
dramatic orchestral bed @ 0.30
neon
, huge (fontSize ~4.5rem, near the 5.0rem max), color-coded by topic, positionY 0.85
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)
youtube-long
1 change every 5–7s — contemplative, give diagrams time to read
naturalEnhanced
@ 0.4
ambient / orchestral @ 0.12
minimal
@ positionY 0.85
Explanatory diagrams as motion graphics (labeled SVGs with
power2.inOut
reveals,
stagger: 0.15
on labels) · chapter dividers with chrome-gradient section titles · one or two hero stat-reveals with counter tweens · outro: citations card + subscribe
Vox / Johnny Harris (explainer / essay long-form)
youtube-long
1 change every 4–6s — narrative-driven
cinematicTealOrange
@ 0.7
cinematic bed @ 0.18
minimal
@ positionY 0.85
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
reference/motion-philosophy.md
. 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
  • 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
transcript.remove-fillers
,
transcript.remove-silences
,
transcript.delete-words
, or
transcript.find-replace
, new trim regions get added, the edited-time map shifts, and any region whose
startMs
/
endMs
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.
The fix is the
--anchorSourceMs
argument.
When you derive
atMs
/
startMs
from a transcript word's source time, pass that same value as
--anchorSourceMs
. 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.
v1.35.0+: every region is auto-anchored on creation, even without
--anchorSourceMs
.
The primitive now back-computes a source-time anchor from the resolved edited
atMs
(via
editedToSource
) 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 pass
--anchorSourceMs
when 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.
type: "free"
anchors are preserved as opt-outs.
Verbs that accept anchors (use them ALWAYS when picking from transcript):
VerbAnchor argsWhen required
project.add-zoom
--anchorSourceMs
,
--anchorSourceEndMs
Always when atMs comes from a transcript word
project.add-motion-graphic
--anchorSourceMs
,
--anchorSourceEndMs
Always when atMs comes from a transcript word
project.add-lower-third
--anchorSourceMs
,
--anchorSourceEndMs
Always when atMs comes from a transcript word
project.add-annotation
--anchorSourceMs
,
--anchorSourceEndMs
Always when startMs comes from a transcript word
project.add-audio
--anchorSourceMs
,
--anchorSourceEndMs
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'
sourceDurationMs
to convert an in-clip offset to global source time before passing as
--anchorSourceMs
. Use
timeline.source-to-edited
to verify where a source-time position falls on the edited timeline.
The 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.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

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

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

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

3. POLISH — skip sections by profile:

-
shorts
: no lower thirds (tight vertical frame)

-
loom
: skip 3b, 3c entirely

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

(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

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

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
.

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

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={}
undefined

Performance — keep wall-clock minimal

Motion-graphic renders dominate (each scene ~20–45s); everything else is rounding error. The levers:
  • motion.render-html
    renders are SERIAL — fire ONE,
    job.wait
    it, then the next.
    A single-render mutex serializes them; a second concurrent render returns
    { ok:false, error:"RENDER_BUSY" }
    (parallel renders share GPU / scratch / headless-shell state and wedge). Do NOT fire all renders at once.
  • Run
    audio.clean
    in the background
    (capture its jobId right after transcribe;
    job.wait
    only before
    export.start
    ). This IS safe to overlap with a render — the serial limit is render-to-render only.
  • Pre-flight HTML with
    motion.screenshot
    before a full render — a ~2s screenshot at
    --atMs=<mid-scene>
    beats 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).
  • Re-read sparingly: pass
    --includeTranscript=false
    after the first
    project.read
    , and reuse the
    { project }
    each mutation returns instead of re-reading.

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
    shorts
    , where 1 per 5–10s is fine)
  • 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
    youtube-long
    defaults to a
    shorts
    project
    — wrong aspect, music too quiet, captions too subtle, pacing too slow
  • Motion graphics in
    loom
    — kills the "this is a quick update" vibe

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 saysResolve to
"edit this" / "polish this" / "make it engaging" / "make it ready"
youtube-long
, no style override
"YouTube-ready" / "make a YouTube video" / "edit for YouTube"
youtube-long
, no style override
"make it a Short" / "TikTok" / "Reel" / "vertical" / "9:16"
shorts
, no style override
"for LinkedIn"
linkedin
, no style override
"Loom" / "internal update" / "just cut the fluff"
loom
, no style override
"edit like Ali Abdaal" / "Ali Abdaal style" / "tutorial style" / "productivity video"
youtube-long
+ Ali Abdaal override
"MKBHD style" / "tech review style" / "product review"
youtube-long
+ MKBHD override
"MrBeast style" / "high-retention" / "challenge video" / "maximum engagement"
youtube-long
+ MrBeast override
"Veritasium style" / "educational" / "Kurzgesagt vibe" / "explainer"
youtube-long
+ Veritasium override
"Vox style" / "essay" / "narrative" / "Johnny Harris style"
youtube-long
+ Vox override
If the user doesn't name a style and doesn't specify a destination, the safe default is
youtube-long
with no style override — the most common case by far.

Pattern: one-shot execution

After entry trigger + profile/style resolution, announce the plan in one sentence and execute. Load
reference/motion-philosophy.md
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.
I'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,
modernVibrant
LUT at 0.5, warm ambient music at 0.15,
bold
captions 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.
I'll edit this as a Short — aggressive pacing (hook in 3s, 6–12 zooms/min),
modernVibrant
LUT at full intensity,
neon
captions positioned higher, music at 30%. No intro card or lower thirds — they don't fit the vertical frame. Frame-verify before export. ~2 minutes.
I'll edit this as a MrBeast-style YouTube video — 2–3s pacing (very fast),
warmSunset
LUT at 0.8, dramatic orchestral bed at 0.30, huge color-coded
neon
captions, 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.
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:
agent.session-list
(id, title, timestamps; returns
running:false
when the embedded agent server is down — it never starts it) and
agent.session-stop --sessionId=<id>
(or
--all=true
) to abort execution while keeping the transcript. Use when the user reports the in-app agent doing something unattended.

What this skill is NOT for

  • Cloud video APIs (HeyGen, Runway, Sora). PandaStudio is local-only.
  • Direct edits to
    .pandastudio
    project JSON.
    The format is owned by the editor and changes between versions. Use
    project.read
    /
    project.save
    and treat the JSON as opaque between reads.
  • Cloud video APIs — PandaStudio is local-only;
    export.start
    renders on the user's machine.

Reference files

  • reference/commands.md
    — every verb.noun with arg schema and a one-line example.
  • reference/examples.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
    motion.render-html
    graphic.
  • reference/templates.md
    — what each motion-graphic template looks like, with the slots it accepts and which aspect ratios it supports.
  • reference/motion-philosophy.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/video-authoring.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/promo-and-mg-videos.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/motion-recipes.md
    — menu of ~30 named, seek-safe motion patterns + scene transitions + the determinism guardrails (incl. the
    data-duration
    /
    data-start
    are-in-SECONDS rule, the GSAP-must-load rule, and the motion-quality gate). Read when authoring custom motion for a specific beat.
  • reference/whiteboard-style.md
    — 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
    svgOrigin
    gotcha). Load for "whiteboard animation / hand-drawn / sketch / doodle" briefs and from-scratch concept explainers (science, process, metaphor).
  • reference/motion-templates.md
    — background modes, designed segments, and the full bundled-template catalog (incl. podcast layouts). Read when picking/rendering a template via
    motion.generate
    .
  • reference/custom-html.md
    — the render verbs (
    motion.screenshot
    /
    render-html
    /
    concat
    ), transparent overlays + frosted glass, add-by-jobId. Read alongside motion-philosophy when authoring custom HTML.
  • reference/visual-edits.md
    — zooms (incl. follow-cursor +
    --anchorSourceMs
    ), trims, speed, crop/reframe, face centering, webcam + per-section podcast layouts, speaker-driven editing. Read for any visual edit.
  • reference/audio-color-music.md
    audio.clean
    , background-audio regions, bundled + Lyria music, and LUT color presets.
  • reference/captions-metadata.md
    — captions (toggle/style/font), AI title/description/timestamps, YouTube thumbnails.
  • reference/fx-transitions.md
    — scene transitions + FX overlays, with the restraint rules.
  • reference/media-generation.md
    — Replicate narration (TTS) and B-roll image generation (always Ken-Burns a still).
  • reference/shorts.md
    — turn an export into vertical clips: discover shots, fork per shot, the 9:16 playbook, batch.
  • reference/publishing.md
    — connect + publish to YouTube and Instagram, with the privacy/account/workspace hard rules.
  • reference/projects-and-transcription.md
    — folders, rename, transcription languages, transcribing a standalone file.