Éditeur Vidéo IA — Référence de Contrôle Agent
⚠️ TROUVER editorctl — OBLIGATOIRE (lire ceci d'abord)
editorctl est l'interface CLI de Monet. Essayez-la toujours directement en premier — Monet la garde sur le PATH pour chaque shell qu'il lance et persiste le répertoire binaire dans ~/.zshrc / ~/.bash_profile pour qu'elle survive aux sous-shells et aux wrappers qui réécrivent le PATH. Si command -v editorctl ne retourne rien, passez à la solution de repli dans l'ordre :
"$HOME/Library/Application Support/Monet/bin/editorctl"— installation macOS (la plupart des utilisateurs)node /Applications/Monet.app/Contents/Resources/app.asar.unpacked/out/cli/cli/editorctl.js …node ./out/cli/cli/editorctl.js …— arbre de développement
N'abandonnez jamais après le premier "command not found". Préférez editorctl à curl localhost:51847 brut — il expose toujours la surface de commande et les formes d'arguments actuelles.
⚠️ NOMENCLATURE DES FICHIERS DE SORTIE — OBLIGATOIRE (lire ceci d'abord)
Ne réutilisez jamais un nom de fichier lors de l'édition ou de la régénération d'une vidéo ou d'une image. Le cache des ressources conserve le fichier précédent par chemin, donc écrire dans le même nom affiche silencieusement un contenu obsolète. Chaque édition/régénération doit produire un nouveau nom de fichier unique (par exemple clip_v1.mp4, clip_v2.mp4, ou un suffixe temporel). Cela s'applique à tous les rendus vidéo, générations d'images, exportations de canvas et miniatures. Si un chemin cible existe déjà, ajoutez _v2, _v3, … — ne l'écrasez pas.
⚠️ AUDIO SUR LES TIMELINES MULTI-CLIPS — OBLIGATOIRE
Lors de l'ajout d'audio à une timeline avec plus d'un clip vidéo, l'audio est coupé à chaque limite de clip. Flux requis :
- Informez l'utilisateur : "Pour empêcher l'audio de se couper entre les clips, je vais d'abord fusionner tous les clips vidéo en une seule vidéo combinée, puis ajouter l'audio. OK pour continuer ?"
- Attendez la confirmation — ne fusionnez pas automatiquement.
- Concaténez tous les clips en un seul nouveau fichier (nom unique selon la règle ci-dessus).
- Remplacez la piste vidéo multi-clips par ce clip fusionné, puis ajoutez l'audio.
Timelines à clip unique : ignorez la fusion.
⚠️ MODE CANVAS — RÈGLES OBLIGATOIRES (lire ceci d'abord)
Lorsque activeView=canvas est signalé par le hook ou editorctl get-state :
Il y a EXACTEMENT 3 options canvas. Aucune autre n'existe.
- Paper.js — dessin de code avec graphiques vectoriels (
canvas-run-paperjs) - Matter.js — physique et animation (
canvas-run-matterjs) - Image GPT 2 — image générée par IA (
generate-image+canvas-add-image)
SUPPRIMÉES — ne proposez PAS celles-ci sous quelque nom que ce soit :
Mode design— suppriméCalques modifiables— suppriméDisposition Figma— suppriméDesign basé sur les nœuds— suppriméConception directe du canvas— supprimé
Lorsque l'utilisateur demande quelque chose de visuel en mode canvas, présentez UNIQUEMENT ces trois options en utilisant ces ÉTIQUETTES EXACTES. Copiez-les textuellement. N'inventez pas une quatrième option et ne reformulez pas l'option 1 comme quelque chose de lié au design.
Pont API : POST http://localhost:51847 — Corps 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") # → liste complète des commandes
Commandes
Projet / Paramètres
| Commande | Arguments | Retourne |
|---|---|---|
get_project | — | Projet JSON complet |
get_settings | — | Configuration du modèle + fournisseur |
Ressources
| Commande | Arguments | Retourne |
|---|---|---|
list_assets | — | [{id, name, path, type, duration, semantic}] |
get_asset | assetId | Ressource unique avec sémantique + transcription |
import_files | paths: string[] | Enregistrements de ressources importées |
transcribe_asset | assetId, language? | {segments} via Whisper |
embed_assets | all?: bool | {embedded, total} via text-embedding-3-small |
search_media | query, limit? | Recherche de similarité cosinus (repli sur mot-clé) |
search_spoken | query, limit? | Recherche de sous-chaîne dans les segments transcrits |
Séquences et Pistes
| Commande | Arguments | Retourne |
|---|---|---|
list_sequences | — | Toutes les séquences |
create_sequence | name | Nouvelle séquence |
activate_sequence | sequenceId | Séquence activée |
get_tracks | — | [{id, name, kind, clipCount}] pour la séquence active |
add_track | `kind: video | audio |
Clips
| Commande | Arguments | Retourne |
|---|---|---|
list_clips | sequenceId? | Tous les clips triés par startTime, avec 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? | Clip mis à jour |
split_clip | clipId, time | Séquence mise à jour |
duplicate_clip | clipId, offsetSeconds? | {clipId, clip} |
update_clip_label | clipId, label | {success} |
Effets et Propriétés
| Commande | Arguments | Retourne |
|---|---|---|
list_effects | — | Effets disponibles + documentation des paramètres |
add_effect | clipId, effectType, parameters | Projet mis à jour |
remove_effect | clipId, effectId | Projet mis à jour |
set_speed | clipId, speed | Projet mis à jour (lecture 0.1–10×) |
set_volume | clipId, volume | Projet mis à jour (0–4, 1=normal) |
set_transition | `clipId, side: in | out, type, duration?` |
Historique
| Commande | Arguments |
|---|---|
undo | — |
redo | — |
Types d'Effets
| Type | Paramètres |
|---|---|
fade_in | duration (sec) |
fade_out | duration (sec) |
color_grade | brightness (-1→1), contrast (0.5→2), saturation (0→3) |
blur | radius (px, défaut 5) |
sharpen | amount (0→3, défaut 1) |
Types de Transitions
crossfade · dip_to_black · wipe · slide
Concepts Clés
inPoint= position dans le fichier source où la lecture du clip commencestartTime= position sur la timeline où le clip est placéduration= durée de lecture du clip (peut être plus courte que la source)- Chaque mutation pousse instantanément
project:updated→ l'interface utilisateur reflète les changements en direct - Les effets sont prévisualisés en temps réel (CSS) et cuits via FFmpeg lors de l'exportation
Règles de Sécurité d'Édition
- Traitez les espaces vides dans la timeline comme un bogue, sauf si l'utilisateur demande explicitement une pause, un temps, ou un espace noir.
- Après avoir créé une séquence ou déplacé des clips, vérifiez la continuité avec
list_clips(sequenceId)et assurez-vous que chaque clip commence exactement là où le clip précédent se termine si l'édition est censée être continue. - Ne supposez pas que
create_sequenceouactivate_sequencesuffit seul. Vérifiez toujours la séquence active ensuite aveclist_sequencesouget_project. - Lors de la construction d'un montage de moments forts parlés, déplacez la vidéo, l'audio, les légendes et les marqueurs ensemble. Ne laissez pas de légendes ou de marqueurs sur d'anciens timings après avoir resserré le montage.
- Si la lecture semble s'arrêter entre les clips, inspectez d'abord la timeline pour détecter des espaces vides avant de supposer que le lecteur d'aperçu est cassé.
- Préférez des débuts cibles exacts plutôt que des calculs de décalage cumulatifs lors du resserrement d'un montage. Recalculez le
startTimeprévu de chaque clip à partir du plan de montage.
Esthétique de Marque à partir d'une URL
OBLIGATOIRE — AUCUNE EXCEPTION : Si le message de l'utilisateur contient une URL ou un nom de domaine, vous DEVEZ exécuter les commandes de récupération ci-dessous et extraire les tokens de marque avant d'écrire tout code de canvas/design. N'utilisez pas de couleurs de marque supposées ou mémorisées. Récupérez d'abord, concevez ensuite — à chaque fois. Si la récupération échoue, demandez à l'utilisateur de coller les valeurs hexadécimales ; ne recourez jamais à des suppositions.
curl -sL "<url>" | grep -Eo '(#[0-9a-fA-F]{3,8}|font-family:[^";}]+|font-size:[^";}]+|border-radius:[^";}]+)' | sort -u | head -60
Extrayez : couleurs d'arrière-plan, couleurs de surface, couleurs primaires/d'accentuation, couleurs de texte, polices de caractères, tailles de police/poids, rayons de bordure, espacements et ombres de boîte. Trouvez également le logo — c'est la vérité terrain pour les couleurs de la marque et le ton visuel. Codez ces valeurs exactes — ne devinez jamais lorsqu'une URL réelle est disponible.
# Trouver le logo
curl -sL "<url>" | grep -Eo '(src|href)="[^"]*logo[^"]*"' | head -10
curl -sL "<url>" | grep 'og:image'
Canvas — Images Physiques Matter.js
Utilisez canvas_run_matterjs pour placer une scène physique interactive en direct sur le canvas.
Outils MCP
| Outil | Arguments |
|---|---|
canvas_run_matterjs | id? (id de l'image à mettre à jour), name?, script (chaîne 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}]} |
Modèles de script — choisissez-en UN
Modèle A — Autonome (interfaces utilisateur riches, dessin 2D personnalisé, boucle 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'); // ← utilisez toujours ceci ; ne créez jamais de nouveau canvas
canvas.width = W; canvas.height = H;
const ctx = canvas.getContext('2d');
// ... ajoutez des corps, configurez la souris ...
function loop() {
Engine.update(engine, 1000/60);
ctx.fillStyle = '#121212'; ctx.fillRect(0, 0, W, H);
// dessinez avec ctx
requestAnimationFrame(loop);
}
loop();
Modèle B — Corps uniquement (le modèle gère engine/render/runner)
// engine, render, width, height, et toutes les variables Matter sont déjà déclarées — ne les redéclarez PAS
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 et Render.run sont appelés automatiquement après ce script — ne les appelez pas ici
Règles critiques
Runner.runnécessite deux arguments :Runner.run(Runner.create(), engine)—Runner.run(engine)à un seul argument ne fait rien silencieusement dans Matter.js 0.20.0- Ne jamais utiliser
element: document.bodydansRender.create— cela crée un deuxième canvas invisible ; utilisezcanvas: document.querySelector('canvas')à la place - Ne jamais déclarer
const engineoulet enginedans le Modèle B — le modèle a déjàvar engine; le redéclarer avecconst/letprovoque une SyntaxError qui vide silencieusement le canvas - Ne jamais appeler
Render.run/Runner.rundans le Modèle B — le modèle le fait après l'exécution de votre script ; les appeler deux fois provoque une vitesse double ou un rendu cassé - Ne jamais importer ou redéclarer les variables Matter dans le Modèle B —
Engine,Bodies,Composite, etc. sont déjà déstructurés ;const { Engine } = Matterprovoquera une SyntaxError
Variables globales disponibles dans le Modèle B
engine · render · width · height · Engine · Render · Runner · Bodies · Composite · World · Body · Events · Constraint · Mouse · MouseConstraint
CLI
editorctl canvas-add-frame "Nom" 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 — Composition Vidéo React
Utilisez Remotion pour générer des ressources vidéo animées (cartes de titre, tiers inférieurs, diaporamas, graphiques animés) et les importer directement dans la timeline.
Outils MCP
| Outil | Arguments | Retourne |
|---|---|---|
video_editor_list_remotion_compositions | — | Liste des IDs de composition |
video_editor_render_remotion | compositionId, outputFilename?, props?, durationInFrames?, fps? | {outputPath, assetId} |
CLI
npm run remotion:studio # prévisualisation en direct à localhost:3000
npx remotion render remotion/src/index.ts <ID> out.mp4 --props '{"key":"value"}'
Compositions intégrées
| ID | Propriétés |
|---|---|
TitleCard | title (str), subtitle? (str), backgroundColor, textColor, accentColor — 150 images @ 30ips |
Slideshow | images (chemins abs[]), frameDuration (images), transitionDuration (images) — 300 images @ 30ips |
HtmlInCanvasGlitch | title, subtitle, glitchIntensity (0–40), backgroundColor, textColor, accentColor — 180 images @ 30ips. Glitch RVB via <HtmlInCanvas> |
HTML-dans-canvas (Remotion ≥ 4.0.455)
⚠️ Conflit de dénomination — lisez d'abord. Le
<HtmlInCanvas>de Remotion (cette section) produit un fichier vidéo pour la timeline. Les images HTML du canvas Monet (canvas-add-frame ... html,canvas_update_frame) produisent une scène HTML en direct dans le tableau de bord de l'onglet canvas. Systèmes différents. Choisissez selon la destination : timeline → Remotion ; onglet canvas → image HTML Monet. Ne mélangez jamais les deux dans une même tâche.
Utilisez <HtmlInCanvas> pour dessiner une sous-arborescence DOM dans un <canvas> et post-traiter avec Canvas 2D / WebGL / WebGPU. Idéal pour les effets de glitch, loupe, CRT, déplacement, rotation de teinte.
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() // <-- toujours réappliquer
}
<HtmlInCanvas width={1920} height={1080} onPaint={onPaint}>
<YourDomTree />
</HtmlInCanvas>
Règles :
- Toujours réappliquer la valeur de retour de
drawElementImageàelement.style.transformpour que la disposition et les entrées restent correctes. - Ne jamais imbriquer
<HtmlInCanvas>dans un autre — Chrome ne peint que l'extérieur et Remotion lève une erreur. Fusionnez les effets dans un seulonPaint. Config.setChromiumOpenGlRenderer('angle')est déjà défini dans