AI 影片編輯器 — 代理控制參考
⚠️ 尋找 editorctl — 強制規定(請先閱讀此項)
editorctl 是 Monet 的命令列工具。始終優先直接嘗試它 — Monet 會將其保留在每個 spawned shell 的 PATH 中,並將 bin 目錄持久化到 ~/.zshrc / ~/.bash_profile,使其能存活於 subshell 和 PATH 重寫包裝器中。如果 command -v editorctl 沒有傳回任何結果,請依序降級嘗試:
"$HOME/Library/Application Support/Monet/bin/editorctl"— macOS 安裝(大多數使用者)node /Applications/Monet.app/Contents/Resources/app.asar.unpacked/out/cli/cli/editorctl.js …node ./out/cli/cli/editorctl.js …— 開發樹
永遠不要在第一次「command not found」後就放棄。優先使用 editorctl 而非直接操作 curl localhost:51847 — 它總會暴露當前命令表面和參數形狀。
⚠️ 輸出檔案命名 — 強制規定(請先閱讀此項)
在編輯或重新生成影片或影像時,絕對不要重複使用檔名。 素材快取會保留前一路徑的檔案,因此寫入相同名稱會靜默顯示過期內容。每次編輯/重新生成都必須產生新的唯一檔名(例如 clip_v1.mp4、clip_v2.mp4,或使用時間戳後綴)。適用於所有影片渲染、影像生成、畫布匯出和縮圖。如果目標路徑已存在,請附加 _v2、_v3 … — 請勿覆蓋。
⚠️ 多剪輯時間軸的音訊 — 強制規定
在向包含多個影片剪輯的時間軸加入音訊時,音訊會在每個剪輯邊界處中斷。必要流程:
- 告知使用者:「為避免音訊在剪輯間中斷,我將先將所有影片剪輯合併為一個組合影片,然後再加入音訊。可以繼續嗎?」
- 等待使用者確認 — 不要自動合併。
- 將所有剪輯串接成一個新的單一檔案(遵循上述規則使用唯一檔名)。
- 用該合併後的剪輯取代多剪輯影片軌道,然後再加入音訊。
單一剪輯時間軸:跳過合併步驟。
⚠️ 畫布模式(Canvas Mode)— 強制規則(請先閱讀此項)
當 hook 或 editorctl get-state 回報 activeView=canvas 時:
只有精確的 3 個畫布選項。不存在其他選項。
- Paper.js — 使用向量圖形進行程式碼繪圖(
canvas-run-paperjs) - Matter.js — 物理與動畫(
canvas-run-matterjs) - GPT image 2 — AI 生成影像(
generate-image+canvas-add-image)
已移除 — 請勿以任何名稱提供下列選項:
設計模式— 已移除可編輯圖層— 已移除類 Figma 布局— 已移除基於節點的設計— 已移除直接畫布設計— 已移除
當使用者在畫布模式中要求視覺相關功能時,僅提供這三個選項,並使用這些精確標籤。逐字複製它們。不要發明第四個選項,也不要將選項 1 重新表述為任何與設計相關的內容。
API 橋接(API Bridge): POST http://localhost:51847 — JSON body {"command":"<name>","args":{...}}
import urllib.request, json
def call(command, **args):
data = json.dumps({"command": command, "args": args}).encode()
req = urllib.request.Request(
"http://localhost:51847", data=data,
headers={"Content-Type": "application/json"}, method="POST"
)
return json.loads(urllib.request.urlopen(req).read())["result"]
call("ping") # → {"status":"ok","version":"1.0.0","port":51847}
call("help") # → 完整命令列表
命令
專案 / 設定
| 命令 | 參數 | 回傳值 |
|---|---|---|
get_project | — | 完整專案 JSON |
get_settings | — | 模型 + 提供者配置 |
素材
| 命令 | 參數 | 回傳值 |
|---|---|---|
list_assets | — | [{id, name, path, type, duration, semantic}] |
get_asset | assetId | 單一素材,含語意 + 逐字稿 |
import_files | paths: string[] | 已匯入的素材記錄 |
transcribe_asset | assetId, language? | {segments}(透過 Whisper) |
embed_assets | all?: bool | {embedded, total}(透過 text-embedding-3-small) |
search_media | query, limit? | 餘弦相似度搜尋(降級為關鍵字搜尋) |
search_spoken | query, limit? | 在轉錄片段內進行子字串搜尋 |
序列與軌道
| 命令 | 參數 | 回傳值 |
|---|---|---|
list_sequences | — | 所有序列 |
create_sequence | name | 新序列 |
activate_sequence | sequenceId | 已啟用的序列 |
get_tracks | — | 作用中序列的 [{id, name, kind, clipCount}] |
add_track | kind: video|audio|caption | 更新後的序列 |
剪輯
| 命令 | 參數 | 回傳值 |
|---|---|---|
list_clips | sequenceId? | 所有剪輯(依 startTime 排序),含 trackKind |
add_clip | assetId, trackId, startTime, duration?, inPoint? | {clipId, clip} |
remove_clip | clipId | {success} |
move_clip | clipId, startTime | {success} |
trim_clip | clipId, inPoint?, duration?, startTime? | 更新後的剪輯 |
split_clip | clipId, time | 更新後的序列 |
duplicate_clip | clipId, offsetSeconds? | {clipId, clip} |
update_clip_label | clipId, label | {success} |
特效與屬性
| 命令 | 參數 | 回傳值 |
|---|---|---|
list_effects | — | 可用特效 + 參數文件 |
add_effect | clipId, effectType, parameters | 更新後的專案 |
remove_effect | clipId, effectId | 更新後的專案 |
set_speed | clipId, speed | 更新後的專案(0.1–10 倍播放) |
set_volume | clipId, volume | 更新後的專案(0–4,1=正常) |
set_transition | clipId, side: in|out, type, duration? | {success} |
歷史
| 命令 | 參數 |
|---|---|
undo | — |
redo | — |
特效類型
| 類型 | 參數 |
|---|---|
fade_in | duration(秒) |
fade_out | duration(秒) |
color_grade | brightness(-1→1)、contrast(0.5→2)、saturation(0→3) |
blur | radius(像素,預設 5) |
sharpen | amount(0→3,預設 1) |
轉場類型
crossfade · dip_to_black · wipe · slide
關鍵概念
inPoint= 剪輯播放開始時在原始檔案中的位置startTime= 剪輯在時間軸上放置的位置duration= 剪輯的播放時長(可以比原始檔案短)- 每次變更都會立即推送
project:updated→ UI 即時反映變更 - 特效會即時預覽(CSS)並在匯出時透過 FFmpeg 固定
編輯安全規則
- 除非使用者明確要求暫停、節拍或黑畫面間隙,否則將時間軸間隙視為錯誤。
- 在建立序列或移動剪輯後,請使用
list_clips(sequenceId)驗證連續性,並檢查若編輯旨在連續時,每個剪輯是否恰好在前一個剪輯結束時開始。 - 不要假設
create_sequence或activate_sequence就足夠了。務必在後續使用list_sequences或get_project驗證作用中序列。 - 在建立語音精華剪輯時,請一併移動影片、音訊、字幕和標記。在緊湊編輯後,不要將字幕或標記留在舊的時間點上。
- 如果播放似乎在剪輯間停止,請先檢查時間軸是否有空白間隙,再假設預覽播放器故障。
- 在緊湊剪輯時,優先使用精確的目標起始時間,而非累積偏移計算。從剪輯計畫重新計算每個剪輯的預期
startTime。
從 URL 提取品牌美學
強制規定 — 無例外:如果使用者的訊息包含任何 URL 或網域,你必須先執行下列 fetch 命令並提取品牌標記,才能撰寫任何畫布/設計程式碼。不要使用假設或記憶的品牌顏色。先 fetch,再設計 — 每次都如此。如果 fetch 失敗,請要求使用者貼上十六進位值;永遠不要降級為猜測。
curl -sL "<url>" | grep -Eo '(#[0-9a-fA-F]{3,8}|font-family:[^";}]+|font-size:[^";}]+|border-radius:[^";}]+)' | sort -u | head -60
提取內容:背景顏色、表面顏色、主要/強調顏色、文字顏色、字型系列、字型大小/粗細、邊框圓角、間距和方塊陰影。同時找到標誌 — 它是品牌顏色和視覺色調的真實依據。將這些確切值硬編碼 — 當有真實 URL 可用時,永遠不要猜測。
# 尋找標誌
curl -sL "<url>" | grep -Eo '(src|href)="[^"]*logo[^"]*"' | head -10
curl -sL "<url>" | grep 'og:image'
畫布 — Matter.js 物理幀
使用 canvas_run_matterjs 在畫布上放置一個即時互動的物理場景。
MCP 工具
| 工具 | 參數 |
|---|---|
canvas_run_matterjs | id?(要更新的幀 ID)、name?、script(JS 字串)、width?、height? |
canvas_add_frame | name、width、height、mode: 'matterjs' |
canvas_matterjs_scene | style(balls/stacks/pendulum/bridge/ragdoll)、gravity?、count?、colors? |
canvas_frames | — → {frames: [{id, name, mode, width, height}]} |
腳本模式 — 選擇一種
模式 A — 自包含(豐富 UI、自訂 2D 繪圖、rAF 迴圈)
const { Engine, Bodies, Composite, Mouse, MouseConstraint, Events } = Matter;
const W = 390, H = 844;
const engine = Engine.create({ gravity: { y: 1.5 } });
const canvas = document.querySelector('canvas'); // ← 始終使用此項;永遠不要建立新的
canvas.width = W; canvas.height = H;
const ctx = canvas.getContext('2d');
// ... 加入物體,設定滑鼠 ...
function loop() {
Engine.update(engine, 1000/60);
ctx.fillStyle = '#121212'; ctx.fillRect(0, 0, W, H);
// 使用 ctx 繪圖
requestAnimationFrame(loop);
}
loop();
模式 B — 僅物體(模板管理 engine/render/runner)
// engine, render, width, height, 以及所有 Matter 變數皆已宣告 — 請勿重新宣告它們
var ground = Bodies.rectangle(width/2, height+25, width, 50, { isStatic: true, render: { fillStyle: '#334155' } });
var ball = Bodies.circle(width/2, 50, 30, { restitution: 0.8, render: { fillStyle: '#5b82f7' } });
Composite.add(engine.world, [ground, ball]);
engine.gravity.y = 1;
// Runner.run 和 Render.run 會在此腳本後自動呼叫 — 請勿在此處呼叫它們
關鍵規則
Runner.run需要兩個參數:Runner.run(Runner.create(), engine)— 在 Matter.js 0.20.0 中,單參數Runner.run(engine)會靜默無效- 在
Render.create中永遠不要element: document.body— 會產生不可見的第二個畫布;改用canvas: document.querySelector('canvas') - 在模式 B 中永遠不要宣告
const engine或let engine— 模板已經有var engine;使用const/let重新宣告會導致 SyntaxError,使畫布靜默空白 - 在模式 B 中永遠不要呼叫
Render.run/Runner.run— 模板會在你的腳本執行後執行這些操作;呼叫兩次會導致雙倍速度或渲染中斷 - 在模式 B 中永遠不要匯入或重新宣告 Matter 變數 —
Engine、Bodies、Composite等已被解構;const { Engine } = Matter會導致 SyntaxError
模式 B 可用全域變數
engine · render · width · height · Engine · Render · Runner · Bodies · Composite · World · Body · Events · Constraint · Mouse · MouseConstraint
CLI
editorctl canvas-add-frame "Name" 390 844 matterjs # matterjs|paperjs|html
editorctl canvas-run-matterjs <frameId> "$(cat script.js)"
editorctl canvas-run-paperjs <frameId> "$(cat script.js)"
editorctl canvas-run-html <frameId> "$(cat scene.html)"
editorctl canvas-frames
editorctl canvas-done
Remotion — React 影片合成
使用 Remotion 生成動畫影片素材(標題卡、下三分之一、幻燈片、動態圖形),並直接匯入時間軸。
MCP 工具
| 工具 | 參數 | 回傳值 |
|---|---|---|
video_editor_list_remotion_compositions | — | 合成 ID 列表 |
video_editor_render_remotion | compositionId, outputFilename?, props?, durationInFrames?, fps? | {outputPath, assetId} |
CLI
npm run remotion:studio # 在 localhost:3000 即時預覽
npx remotion render remotion/src/index.ts <ID> out.mp4 --props '{"key":"value"}'
內建合成
| ID | Props |
|---|---|
TitleCard | title(字串)、subtitle?(字串)、backgroundColor、textColor、accentColor — 150 幀 @ 30fps |
Slideshow | images(絕對路徑[])、frameDuration(幀)、transitionDuration(幀) — 300 幀 @ 30fps |
HtmlInCanvasGlitch | title、subtitle、glitchIntensity(0–40)、backgroundColor、textColor、accentColor — 180 幀 @ 30fps。透過 <HtmlInCanvas> 實現 RGB 分離故障效果 |
HTML-in-canvas(Remotion ≥ 4.0.455)
⚠️ 命名衝突 — 請先閱讀。 Remotion
<HtmlInCanvas>(本節)為時間軸產生影片檔案。Monet 畫布 HTML 幀(canvas-add-frame ... html、canvas_update_frame)在畫布標籤頁的藝術板上產生即時 HTML 場景。不同的系統。根據目的地選擇:時間軸 → Remotion;畫布標籤頁 → Monet HTML 幀。永遠不要在單一任務中混合兩者。
使用 <HtmlInCanvas> 將 DOM 子樹繪製到 <canvas> 中,並使用 Canvas 2D / WebGL / WebGPU 進行後處理。最適合用於故障效果、放大鏡、CRT、置換、色相旋轉效果。
import { HtmlInCanvas, type HtmlInCanvasOnPaint } from 'remotion'
const onPaint: HtmlInCanvasOnPaint = ({ canvas, element, elementImage }) => {
const ctx = canvas.getContext('2d')!
ctx.reset()
ctx.filter = 'blur(8px)'
const transform = ctx.drawElementImage(elementImage, 0, 0)
element.style.transform = transform.toString() // <-- 始終重新套用
}
<HtmlInCanvas width={1920} height={1080} onPaint={onPaint}>
<YourDomTree />
</HtmlInCanvas>
規則:
- 始終將
drawElementImage的回傳值重新套用至element.style.transform,以確保佈局/輸入保持正確。 - 永遠不要在另一個
<HtmlInCanvas>內巢狀使用 — Chrome 只繪製外層,且 Remotion 會拋出錯誤。將效果合併到一個onPaint中。 Config.setChromiumOpenGlRenderer('angle')已在 ...
[截斷以進行翻譯]