AI 비디오 에디터 — 에이전트 제어 레퍼런스
⚠️ editorctl 찾기 — 필수 (먼저 읽으세요)
editorctl은 Monet의 CLI입니다. 먼저 직접 시도하세요 — Monet은 PATH에 항상 유지하며 ~/.zshrc / ~/.bash_profile에 bin 디렉토리를 저장하여 서브셸과 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 …— dev 트리
첫 번째 "command not found" 이후 포기하지 마세요. editorctl을 curl localhost:51847보다 선호하세요 — 항상 현재 명령어 표면과 인자 형태를 노출합니다.
⚠️ 출력 파일 이름 지정 — 필수 (먼저 읽으세요)
비디오나 이미지를 편집하거나 재생성할 때 파일 이름을 재사용하지 마세요. 에셋 캐시는 경로별로 이전 파일을 보관하므로 동일한 이름에 쓰면 이전 콘텐츠가 표시됩니다. 모든 편집/재생성은 새로운 고유 파일 이름을 생성해야 합니다 (예: clip_v1.mp4, clip_v2.mp4 또는 타임스탬프 접미사). 모든 비디오 렌더링, 이미지 생성, 캔버스 내보내기 및 썸네일에 적용됩니다. 대상 경로가 이미 존재하는 경우 _v2, _v3, …을 추가하세요 — 덮어쓰지 마세요.
⚠️ 다중 클립 타임라인의 오디오 — 필수
두 개 이상의 비디오 클립이 있는 타임라인에 오디오를 추가하면 오디오가 모든 클립 경계에서 잘립니다. 필수 흐름:
- 사용자에게 알리세요: "클립 사이에서 오디오가 끊기지 않도록 하기 위해 모든 비디오 클립을 먼저 하나의 결합된 비디오로 병합한 다음 오디오를 추가하겠습니다. 계속할까요?"
- 확인을 기다리세요 — 자동 병합하지 마세요.
- 모든 클립을 위 규칙에 따라 고유한 새 파일로 연결하세요.
- 다중 클립 비디오 트랙을 병합된 클립으로 교체한 다음 오디오를 추가하세요.
단일 클립 타임라인: 병합을 건너뛰세요.
⚠️ 캔버스 모드 — 필수 규칙 (먼저 읽으세요)
activeView=canvas가 훅 또는 editorctl get-state에 의해 보고될 때:
정확히 3개의 캔버스 옵션이 있습니다. 다른 것은 없습니다.
- Paper.js — 벡터 그래픽을 사용한 코드 드로잉 (
canvas-run-paperjs) - Matter.js — 물리 및 애니메이션 (
canvas-run-matterjs) - GPT image 2 — AI 생성 이미지 (
generate-image+canvas-add-image)
제거됨 — 어떤 이름으로도 제공하지 마세요:
디자인 모드— 제거됨편집 가능한 레이어— 제거됨Figma 스타일 레이아웃— 제거됨노드 기반 디자인— 제거됨직접 캔버스 디자인— 제거됨
사용자가 캔버스 모드에서 시각적인 것을 요청하면 이러한 정확한 레이블을 사용하여 이 세 가지 옵션만 제시하세요. 그대로 복사하세요. 네 번째 옵션을 만들거나 옵션 1을 디자인 관련으로 바꾸지 마세요.
API 브리지: POST http://localhost:51847 — JSON 본문 {"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? | Whisper를 통한 {segments} |
embed_assets | all?: bool | text-embedding-3-small을 통한 {embedded, total} |
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 (px, 기본 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이나 도메인이 포함된 경우, 캔버스/디자인 코드를 작성하기 전에 아래 가져오기 명령어를 실행하고 브랜드 토큰을 추출해야 합니다. 가정하거나 기억한 브랜드 색상을 사용하지 마세요. 먼저 가져오고, 그 다음 디자인하세요 — 매번 그렇게 하세요. 가져오기가 실패하면 사용자에게 16진수 값을 붙여넣도록 요청하세요; 추측으로 대체하지 마세요.
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, 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)— 단일 인자Runner.run(engine)은 Matter.js 0.20.0에서 조용히 아무것도 하지 않습니다.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 (str), subtitle? (str), 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>안에<HtmlInCanvas>를 절대 중첩하지 마세요 — Chrome은 외부 것만 렌더링하고 Remotion이 오류를 발생시킵니다. 하나의onPaint로 효과를 병합하세요. Config.setChromiumOpenGlRenderer('angle')은 이미 설정되어 있습니다.