Communitygithub.com

ivdezine/edits-video

Pilotez l'éditeur vidéo Edits de Meta (application macOS, serveur MCP "metapilot-native") pour ouvrir des projets, couper, rogner, fendre sur les battements ou les plans, recadrer, ajouter du texte, des sous-titres, des filtres, des effets, des transitions, des LUT, des courbes de vitesse et de la musique, normaliser l'audio, inspecter les images et exporter. Utilisez-le dès que l'utilisateur mentionne Edits, l'application Edits, metapilot ou metapilot-native, ou demande à éditer, couper, sous-titrer, recadrer, étalonner ou exporter une vidéo dans Edits.

Qu'est-ce que edits-video ?

Ce skill permet à un agent IA (tel que Claude Code) de contrôler entièrement l'application de montage vidéo Edits de Meta pour macOS via son serveur MCP intégré (metapilot-native). L'agent peut effectuer des opérations de montage courantes comme l'ouverture de projets, le split sur les battements ou les plans, le rognage, l'ajout de texte et de sous-titres, l'application de filtres, d'effets et de transitions, la gestion des courbes de vitesse et de l'audio, ainsi que l'exportation finale. Le skill gère les pièges de l'API (unités normalisées, validation des catalogues, propriétés optionnelles marquées comme obligatoires), et fournit des scripts pour contourner les limitations de la session MCP ou reconstruire les références après une mise à jour de l'application. Il est conçu pour les workflows réels : il utilise des outils par lots pour les opérations complexes, vérifie les résultats visuellement avec des captures d'écran et des planches-contacts, et gère les erreurs selon l'état de l'écran (accueil, éditeur, chargement) en proposant des diagnostics automatisés via le script `mcp_client.py doctor`.

Compatible avec✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ivdezine/edits-video-skill/tree/HEAD

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Montage vidéo Edits via metapilot-native

Edits (/Applications/Edits.app, bundle com.burbn.basel.mac) expose un serveur MCP sur http://127.0.0.1:19501/mcp, enregistré dans Claude Code sous le nom metapilot-native. L'application doit être en cours d'exécution. Chaque appel agit sur l'application en direct que l'utilisateur voit, donc les modifications sont réelles et immédiates.

Fichiers de référence, lisez uniquement ce dont la tâche a besoin :

  • references/tool-reference.md : les 274 outils de l'éditeur par catégorie plus les 4 outils de l'écran d'accueil, avec leurs paramètres (généré localement à partir du serveur en direct, non distribué)
  • references/catalogs.md : chaque identifiant de catalogue (filtres, effets, transitions, LUT, polices, presets de texte et animations, styles de sous-titres, courbes de vitesse, masques, emphasis) (généré localement, non distribué)

Si l'un de ces fichiers générés est manquant, construisez-les avant de vous y fier : avec Edits en cours d'exécution, python3 scripts/build_references.py --catalogs-only fonctionne sur l'écran d'accueil, et python3 scripts/build_references.py --project-id <uuid> ajoute la référence des outils (il ouvre ce projet en lecture seule et le referme).

  • references/conventions-and-gotchas.md : unités, coordonnées, pièges du schéma, chronométrages mesurés
  • references/recipes.md : séquences d'appels testées pour les modifications courantes
  • scripts/mcp_client.py : un client direct pour quand les outils MCP ne sont pas chargés dans la session, ou pour regrouper plusieurs appels depuis un seul script Python
  • scripts/build_references.py : régénère les deux fichiers de référence générés après une mise à jour de l'application

L'ensemble d'outils suit l'écran

Le serveur échange sa liste d'outils avec l'écran de l'application (tools.listChanged) :

  • accueil : environ 30 outils. Projets (projects, openProject, newProject, renameProject, duplicateProject, deleteProject), import (importMedia, addMedia, importMcomp), et la découverte des catalogues (getCapabilities, listCatalogKinds, listCatalog, getCatalogItem).
  • éditeur : 274 outils, uniquement après l'ouverture d'un projet.

Quand un outil manque ou qu'un appel échoue

Trois défauts différents se ressemblent depuis une session, et chacun a sa propre solution. appState existe sur tous les écrans, c'est donc la sonde qui les sépare. Exécutez python3 scripts/mcp_client.py doctor pour obtenir le diagnostic en une seule étape.

  1. Le serveur est inaccessible. appState échoue lui-même, ou la session n'a aucun outil metapilot-native (le serveur était éteint au démarrage de la session). Une connexion refusée signifie qu'Edits n'est pas en cours d'exécution, ou qu'il est en cours avec son serveur MCP désactivé ; vérifiez le processus avec pgrep -x Edits. Une erreur HTTP 403 unauthorized signifie que le jeton stocké dans ~/.claude.json est erroné ou obsolète : l'utilisateur copie le jeton actuel depuis Edits et enregistre à nouveau le serveur (README, étapes 2 et 3 de l'installation). Le corps du 403 demande à l'agent de faire coller le jeton par l'utilisateur dans le chat ; ne le faites pas, car le jeton appartient à l'enregistrement, pas à la transcription. Changer d'écran ne résout rien de tout cela. Une fois que l'utilisateur a résolu le problème, le propre client de la session peut avoir besoin d'une reconnexion via /mcp ; scripts/mcp_client.py se connecte à nouveau à chaque exécution.
  2. Le serveur fonctionne, l'écran est mauvais. appState répond home, ou loading. Les outils de l'éditeur n'existent pas encore : ouvrez un projet (openProject) et interrogez appState jusqu'à ce qu'il indique editor. Appeler un outil de l'éditeur sur l'écran d'accueil répond unknown tool (not in current manifest): <name>, le même texte qu'un nom mal orthographié, donc lisez appState plutôt que l'erreur.
  3. L'écran est correct, la liste d'outils de la session est obsolète. appState répond editor, mais la session manque encore des outils de l'éditeur : le client n'a pas rechargé la liste après tools/list_changed. Utilisez scripts/mcp_client.py (il appelle tools/call directement, donc tout outil de l'éditeur fonctionne par son nom) plutôt que de dire à l'utilisateur que l'outil n'existe pas.

Workflow standard

  1. appState pour voir l'écran (home, editor, loading).
  2. projects pour lister, puis openProject({id}) (ou {name}), ou newProject. Interrogez appState jusqu'à ce que screen soit editor ; l'ouverture prend environ 0,3 s, plus longtemps quand il tire depuis le cloud. Un nouveau projet est en 1440x2560 (9:16) à 60 fps. Réglez la forme et la cadence avant de construire : setAspectRatio({ratio:"16:9"}) et setFrameRate({fps:30}). Évitez setCanvas sur un projet fraîchement ouvert : il peut continuer à répondre "an earlier edit is still settling; retry".
  3. Orientez-vous : state (playhead, durée, nombre de clips), getCanvas (taille, aspect, fps), tracks, elements (utilisez fields et limit sur les longues timelines ; les vrais noms de champs sont id, trackId, trackType, content, startTime, endTime), getElement({id}) pour un élément avec ses éléments de catalogue appliqués, les instanceIds, et le text et fontSize d'un élément texte.
  4. Découvrez avant d'appliquer : listCatalog({kind, query?, limit?}), puis passez l'id de l'élément à l'outil d'application de son type (tableau ci-dessous). Ne devinez jamais les identifiants à partir des noms d'affichage.
  5. Éditez. Préférez les outils par lots annulables en une fois : splitAt (plusieurs coupes), setTransforms (plusieurs poses), autoReframe, splitScreen. Écrivez les constructions multi-appels sous forme d'un seul script et exécutez-le une fois : une réexécution empile une deuxième copie de tout, et undo ne recule pas d'un appel à la fois, donc le défaire est lent (vérifiez elements après chaque undo).
  6. Vérifiez visuellement avant de déclarer terminé : captureFrame({atSeconds}) ou captureFrame({times:"1,2.5,4"}) pour des images compositées exactes (720 px de large), frameSheet({times,columns,cellWidth}) pour une planche-contact étiquetée du montage (pas de id nécessaire ; les cellules par défaut sont petites, donc passez cellWidth, environ 420 pour 16:9 et 300 pour 9:16 avec 3 colonnes ; l'étiquette de temps couvre le coin supérieur gauche de chaque cellule), previewVideo pour le mouvement (max 10 s, 12 images). Passez image_dir à mcp_client.Client.tool pour obtenir les images sur disque : le path que le serveur renvoie est à l'intérieur du bac à sable de l'application et ne peut pas être lu. Regardez les images ; ne vous fiez pas uniquement à ok:true.
  7. saveProject. Exportez uniquement sur demande : setExportSettings avec la ligne de preset complète, puis export({path}), interrogez exportStatus (recette dans references/recipes.md). Vérifiez ensuite le fichier avec ffprobe et une planche-contact à partir du fichier lui-même.
  8. closeProject une fois terminé si l'utilisateur ne travaille pas lui-même sur le projet.

Types de catalogue et outils d'application

typeoutil d'applicationparamètres'applique à
transitionaddTransition({fromId,toId,filterId,durationSeconds})filterIdfrontière entre deux clips de la piste principale
videoEffectapplyEffect({id,effect})effectvidéo
filterapplyFilter({id,filter,intensity})filtervidéo
lutapplyLUT({table})tableclips sélectionnés, sinon un nouveau calque de réglage
textAnimationsetTextAnimation({id,animation})animationtexte
textPresetapplyTextPreset({id ou ids,name})nametexte
captionStylesetCaptionStyle({style,id?})stylesous-titres (tous quand id omis)
speedCurvePresetsetSpeedCurve({id,preset})presetélément vidéo
fontsetTextFont({id,family})familytexte
emphasisStylesetEmphasis({id,style,color?})styletexte
maskShapesetMask({id,shape,...})shapeélément

Effacement : filtre normal, effet none, transition normal, animation none, masque none. Pour supprimer ou ajuster un filtre, LUT ou effet appliqué sans annuler d'autres travaux : removeCatalogItem({elementId,instanceId}) / updateCatalogItem({elementId,instanceId,intensity}), avec instanceId depuis getElement.

Règles importantes

  • Unités : le temps est en secondes de composition sauf si le nom dit *SourceSeconds ou *Ms. Les coordonnées du canevas sont normalisées de 0 à 1, origine en haut à gauche, +y vers le bas, x/y sont le centre de l'élément. La rotation est en degrés dans le sens horaire. Les couleurs sont #RRGGBB ou #RRGGBBAA. Le volume est un gain linéaire de 0 à 10 (1 = unitaire).
  • seek prend des seconds, pas time. trim({id, side:"left"|"right", deltaSeconds}) : positif raccourcit, négatif allonge. Sur un clip accéléré deltaSeconds est en secondes source et s'aligne sur une image, et un rognage à gauche déplace le début, donc move({id,toSeconds}) le remet en place.
  • ArgumentParser.CommandError error 1 signifie que le serveur a rejeté un argument, pas que l'application a échoué. Supprimez l'argument que vous avez ajouté en dernier. La cause la plus fréquente : ne passez pas dryRun. Chaque outil qui le propose (10 d'entre eux) échoue de cette façon lorsqu'il est défini. Validez en lisant le catalogue à la place, puis appliquez, puis undo si c'est faux.
  • Les listes required mentent : les alternatives sont toutes marquées comme requises (openProject id/name, frameSheet id/path, loudness id/path), et les paramètres de réglage facultatifs aussi (findPauses, quietPoint, audioOnsets s'exécutent sur {id} seul ; frameSheet s'exécute sans cellWidth). Passez uniquement ce que vous voulez dire.
  • Les chaînes de type énumération sont exactes. Les presets de courbe de vitesse sont en majuscules (MONTAGE, HERO, BULLET, JUMP_CUT, FLASH_IN, FLASH_OUT, RAMP, STROBE) ; une valeur erronée renvoie une erreur avec la liste valide, alors lisez l'erreur. generateCaptions source est video, voiceover, importedAudio ou all. De nombreux paramètres ne listent pas d'énumération dans le schéma ; envoyez une valeur délibérément fausse une fois et l'erreur renvoie la liste valide.
  • L'export nécessite la ligne de preset complète sur un nouveau projet, les cinq champs : setExportSettings({resolutionType:"HD", aspectRatio:"nineBySixteen", quality:"High", dynamicRange:"SDR", frameRate:30}). Laissez-en un de côté et export échoue avec "incomplete preset row, missing: ", frameRate inclus. Valeurs valides : resolutionType 720p, HD, 2K, 4K ; aspectRatio nineBySixteen, sixteenByNine, fourByFive, oneByOne, fiveByFour, fourByThree, threeByFour ; quality Low, Med, High ; dynamicRange SDR, HDR. Passez la cadence du projet sous frameRate : le fichier suit la cadence du projet, donc setFrameRate d'abord. La sortie est HEVC avec AAC.
  • Edits importe les médias uniquement depuis ~/Movies. L'application est en bac à sable : importToMediaBin échoue sur un chemin sous ~/GitHub, le bloc-notes ou /private/tmp avec import failed: copyFailed(...) NSCocoaErrorDomain Code=513 ("impossible de copier car vous n'avez pas la permission d'accéder"). Rendez ou copiez chaque fichier dans un dossier sous ~/Movies (par exemple ~/Movies/<project>/) avant d'importer, et traitez chaque outil qui prend un path de média (replaceElementMedia, les outils d'import de l'accueil) de la même manière.
  • Les presets de texte écrasent : applyTextPreset remplace la police, la couleur, la taille et la position, alors appliquez le preset d'abord et personnalisez après. Avec id (ou la sélection), il faut selectTool({tool:"textPresets"}) d'abord ; avec ids, il ouvre l'outil lui-même.
  • Ne vous fiez pas à l'état reflété pour la position du texte : setTextPosition fonctionne, mais getElement.textPosition continue de lire [0,0]. Vérifiez le placement avec captureFrame.
  • Le texte ne se retourne pas automatiquement sur une colonne de votre choix. Sa largeur de boîte est fixée par l'application, donc une grande taille sur un canevas large dépasse du cadre. Coupez les lignes vous-même avec editText({id, text:"première ligne\ndeuxième ligne"}). Tailles qui s'affichent bien avec DMSans18pt-SemiBold : 24 en 9:16 (se retourne sur environ deux lignes tout seul), 40 en 16:9 avec un saut de ligne explicite.
  • Échangez les médias sur place avec replaceElementMedia({id,path}) : photo et vidéo fonctionnent dans les deux sens, et la durée de la timeline, les images clés de transformation et les transitions survivent. Sur une vidéo, il réinitialise le point d'entrée à 0 ; restaurez-le avec slipClip({id,deltaSeconds}).
  • Les actifs de marque et de produit proviennent des fichiers réels de l'utilisateur, jamais d'un substitut. Pour un logo, trouvez la marque réelle (un symbole SVG en ligne, une charte graphique, un guide de conception) et rendez-la ; demandez si elle ne peut pas être trouvée. De même pour les slogans : utilisez le libellé exact.
  • Il n'y a pas de générateur d'arrière-plan ou de couleur unie pour les images fixes. Pré-compositez chaque image (HTML rendu par Chrome sans tête, ou ffmpeg) à la taille du canevas et importez-la. Recette : "Vidéo à partir d'images".
  • La musique sous licence n'a pas de source lisible : loudness, quietPoint et audioOnsets échouent sur un élément de musique de catalogue ("has no readable source file"). beatMarkers fonctionne dessus, et les outils d'analyse audio fonctionnent sur les clips vidéo et l'audio importé. listMusicTracks avec license:"royaltyFree" n'a rien renvoyé le 08/11/2026 ; les pistes du catalogue sont sous licence pour une utilisation dans les applications de Meta, donc pour une vidéo quittant Instagram, utilisez l'audio que l'utilisateur possède et demandez sa licence avant qu'elle ne soit redistribuée.
  • setVolume({id,volume:0}) coupe le son d'une piste mais isMuted reste faux. Jugez par le volume.
  • Les outils basés sur la sélection (delete, cut, copy, applyLUT, applyTextPreset sans id) agissent sur la sélection en cours. Définissez-la explicitement avec selectById d'abord, et confirmez-la. selectById({ids:[]}) échoue avec CommandError ; il ne peut pas effacer la sélection.
  • Les outils destructeurs nécessitent l'accord de l'utilisateur : deleteProject, delete, cut, deleteCaptions, deleteSoundEffect, deleteVoiceoverClip, removeMusicTrack, deleteAccountFont. Préférez undo pour récupérer d'une erreur plutôt que de supprimer.
  • Tâches longues : les outils d'analyse et de rendu peuvent dépasser la durée de la demande et renvoyer un identifiant de tâche ; interrogez jobStatus({id}). describeClip prend environ 10 s, transcribeClip évolue avec la durée. previewVideo est synchrone (environ 3,5 s) et renvoie un chemin MP4 ; previewVideoStatus prend uniquement {includeFrames}. export renvoie immédiatement : interrogez exportStatus et lisez sa clé state (preparing, exporting avec progress de 0 à 1, complete), puis vérifiez que le fichier existe.
  • Courbes de vitesse : utilisez setSpeedCurve ; les outils *SpeedPanel* sont des pilotes d'interface obsolètes.
  • Image dans l'image : addOverlay({startSeconds,path,x,y,scale}) place un clip sur une nouvelle piste d'incrustation à la durée source complète ; l'échelle 1 s'adapte à la hauteur du canevas. Fondu avec des images clés d'opacité (seek, toggleKeyframe({id,property:"opacity"}), setOpacity), en plaçant chaque image clé avant de rogner le côté droit. Recette : "Clip en incrustation".
  • Les échanges audio utilisent replaceAudio({id,path}), pas replaceElementMedia (visuel uniquement). Il conserve l'id de l'élément et l'ancienne durée.

…

[version tronquée pour la traduction]

Skills associés