Que fait resolve-cut ?
Vous assemblez, rendez et vérifiez des vidéos dans DaVinci Resolve Studio via des scripts sauvegardés avec paramètres. Vous ne réécrivez pas l'assemblage à chaque session : vous appelez les scripts, lisez ce qu'ils renvoient et vérifiez le résultat avec des nombres et des trames. Le jugement reste humain : la personne décide ce qui reste, vous exécutez et mesurez.
$SKILL ci-dessous est le dossier de ce skill (où se trouve ce fichier). Les scripts dans scripts/resolve/ s'exécutent dans Resolve via run.py ; les scripts dans scripts/media/ sont du Python pur avec ffmpeg. Chaque script a --help.
Avant de commencer
- Réponse de Resolve :
python3 "$SKILL/scripts/resolve/run.py" - '{}' <<< 'result = resolve.GetVersionString()'. Le code de sortie 2 signifie que Resolve est fermé, que c'est l'édition gratuite, ou que Préférences > Système > Général > Scripting externe utilisant n'est pas réglé sur Local. ffmpeg -version,python3 -c "import numpy, PIL"etELEVENLABS_API_KEY(environnement ou./.env).- Si le projet conserve un fichier de notes (
VIDEO_NOTES.mdou similaire), lisez-le : ce que la personne a approuvé et rejeté auparavant. Ne demandez pas deux fois la même correction. - Dossier de travail : tout ce que vous écrivez (transcriptions, dossier, trames, rendus) va dans un dossier de travail, jamais dans le dossier des rushes.
Le flux
1. Transcrire. Transcriptions mot pour mot, un JSON par clip :
python3 "$SKILL/scripts/media/transcribe.py" <dossier des clips> --out work/transcripts [--language por].
La transcription est la base de tout : choisir les prises, couper sur les limites de trame et vérifier le mix final.
2. Dossier. Tout le matériel dans un fichier texte compact, lu une fois :
python3 "$SKILL/scripts/media/dossier.py" <dossier des clips> --transcripts work/transcripts --out work/dossier.md [--sheets].
Il liste chaque phrase avec son temps et signale «repeats C0076 20.2» quand une phrase commence comme une précédente : une autre tentative de la même réplique. Avec un script, gardez la dernière prise complète de chaque paragraphe ; supprimez les hésitations, les faux départs et les tentatives répétées. Ouvrez un clip uniquement pour décider quelque chose :
python3 "$SKILL/scripts/media/inspect_range.py" <clip> <t0> <t1> --words work/transcripts/<clip>.json (trames, forme d'onde et mots dans une seule image).
3. Liste de coupes. Écrivez work/plan.json avec les prises choisies dans l'ordre
({"Lesson 01 v1": [["01a", "C0081", 1.9, 12.6], ["01b", "C0083", 5.4, 23.8], ...]} : prise, clip, de, à en secondes du clip), puis :
python3 "$SKILL/scripts/media/cut_list.py" work/plan.json work/transcripts <dossier des clips> work/cuts.json.
Les coupes atterrissent sur la limite de trame la plus silencieuse à côté des mots, les pauses de plus d'1 s sont raccourcies, et les segments alternent le zoom 1 / 1.2 pour que les coupes entre les prises ne sautent pas. Pour un faux départ sans écart ("co- contar"), forcez la trame avec {"in_frame": N} ou {"out_frame": N}. Notes pour le monteur : add_markers.py.
4. Assembler dans Resolve. Une opération lourde par appel, et lisez chaque result avant le suivant :
R="$SKILL/scripts/resolve/run.py"
python3 "$R" "$SKILL/scripts/resolve/project.py" '{"name": "Lesson 01", "width": 1920, "height": 1080, "fps": "25"}'
python3 "$R" "$SKILL/scripts/resolve/assemble.py" '{"phase": "import", "bin": "Raw", "media": "/abs/clips", "files": ["C0081.MP4"]}'
python3 "$R" "$SKILL/scripts/resolve/assemble.py" '{"phase": "timeline", "bin": "Raw", "cuts": "/abs/work/cuts.json", "timeline": "Lesson 01 v1", "zoom": {"center": [0.45, 0.37]}, "voice_isolation": 50}'
zoom.center est l'endroit où se trouve le visage (fraction de la trame), donc le zoom avant le maintient en place. Vérifiez que frames == expected et que failures est vide.
5. Regarder avant le rendu. frames.py exporte les trames directement depuis la timeline en moins d'une seconde
({"timeline": "Lesson 01 v1", "frames": [100, 500], "folder": "/abs/work/frames", "max": 960}). Regardez une trame normale et une trame zoomée.
6. Rendre.
python3 "$R" "$SKILL/scripts/resolve/render.py" '{"phase": "start", "timeline": "Lesson 01 v1", "folder": "/abs/out", "name": "lesson-01", "preset": "reel", "width": 1920, "height": 1080, "mark": [0, <frames - 1>]}',
puis interrogez {"phase": "status", "job": "<id>"} toutes les 10 s jusqu'à ce que JobStatus soit Complete. Attendez la fin du rendu avant de répondre : une exécution sans tête qui termine son tour au "rendu démarré" ne laisse personne pour le vérifier.
7. Vérifier. python3 "$SKILL/scripts/media/qc.py" out/lesson-01.mp4 --expected <frames> --transcribe
(ajoutez --ref contre la dernière version approuvée, --frames pour une feuille, --simulate pour Instagram). Lisez la transcription du mix final à chaque coupe : les mots répétés ("decide, decide"), les mots coupés et les mots au mauvais endroit sont les défauts que crée une liste de coupes. Confirmez une coupe suspecte sur la forme d'onde (inspect_range.py) avant de la déplacer, corrigez la liste de coupes, assemblez une nouvelle version (v2) et rendez à nouveau. Au maximum 3 rounds, chaque défaut nommé avec son temps exact. Ne dites jamais que c'est fini parce que ça "a l'air correct" : citez les chiffres.
8. Livrer. loudness.py quand le mix n'est pas dans la cible (gain linéaire et un limiteur, jamais loudnorm), puis
deliver.py export.mp4 clean.mp4 [--no-editlist] [--copy <folder>] : pas de piste de timecode, pas de métadonnées, moov en premier, copie comparée octet par octet.
9. Réviser, après l'approbation de la personne. Écrivez dans le fichier de notes ce qu'ils ont approuvé et rejeté, dans leurs mots, une ligne chacun ; chaque nouveau piège avec sa date ; et chaque routine que vous avez faite à la main plus d'une fois devient un script avec paramètres à côté de ceux-ci.
Exécution de scripts dans Resolve
run.pyse connecte à Resolve ouvert et exécute un script avecresolve,project,P(le JSON) et__file__définis, puis imprimeresulten JSON. Cela coûte beaucoup moins de tokens que d'écrire le même script via un MCP.- Avec un serveur MCP à la place (officiel
run_script_unsafe) :P = {...}; __file__ = "<abs path of the script>"; exec(open(__file__, encoding="utf-8").read()). Le bac à sable lit les fichiers en ASCII sansencoding, et les noms persistent entre les appels : les scripts préfixent leurs variables. - Sauvegardez avant et après l'écriture (
GetProjectManager().SaveProject()) ; il n'y a pas d'annulation. Faites chaque nouvelle version sur une copie de la timeline (DuplicateTimeline, qui copie aussi les comps Fusion). - Consultez l'API au lieu de deviner :
search_scripting_api,get_scripting_docsetget_whats_newdu MCP officiel (une fois par session), ou le README de Blackmagic dans le dossierDeveloper/Scriptingde Resolve (Aide > Documentation > Développeur). - Nouvelle opération qui se répétera ? Écrivez-la une fois comme un script avec un dictionnaire
Pet unresult, à côté de ceux-ci.
Cibles de livraison
| Destination | Image | Son |
|---|---|---|
| Reels, Stories, Shorts, TikTok | 1440x2560 à la fréquence d'images de tournage, H.264 High à environ 20 Mbps réel, tags Rec.709 (1-1-1), faststart | -14 LUFS, crête réelle sous -1.5 dBTP |
| Leçons et cours | tel qu'enregistré (1920x1080 ou 3840x2160) | -16 LUFS, crête réelle jusqu'à -1 dBTP |
| YouTube | le master 4K (les uploads 1080p perdent en qualité), MP4 sans listes d'édition | le mix du master |
| 1920x1080 et 1080x1920 à 16 Mbps, avec SRT | le mix du master |
Pièges vérifiés
Mesuré sur macOS avec Resolve Studio 21.1.1 et 21.1.2, octobre et novembre 2026.
Machine
- Un seul ffmpeg à la fois, avec
nice -n 15et-threads 2: 96 processus ffmpeg parallèles ont gelé le Mac et forcé un redémarrage. - En zsh,
$varne se divise pas en mots (ffmpeg $argscasse) : construisez les listes d'arguments en Python. - Écrivez les sorties dans le dossier de travail : un assistant qui sauvegardait des images à côté du clip a écrit dans le disque des rushes.
Configuration de Resolve et API
- Resolve 21.1 a déplacé le scripting Python vers Studio :
run.pyet les deux serveurs MCP nécessitent DaVinci Resolve Studio. - Le scripting externe doit être réglé sur Local (Préférences > Système > Général) sinon
scriptapp("Resolve")retourne None. StartRenderingest le dernier appel d'un script : tout le reste retourne None jusqu'à la fin du rendu.- La fréquence d'images du projet se verrouille lorsque la première timeline est créée : définissez-la avant (
project.py). - Résolution :
SetSettingune clé à la fois,useCustomSettingsd'abord. - Le délai d'attente MCP (10 s par défaut, 60 s au maximum) renvoie une erreur mais le script continue de s'exécuter dans Resolve : vérifiez l'état avant de le relancer.
- Il n'y a pas de rogner, déplacer ou glisser : supprimez l'élément et ajoutez-le à nouveau avec
startFrame,endFrame(exclusif) etrecordFrame. startFramecompte à partir de la première trame du fichier, même avec un timecode de caméra.GetSourceStartFramelit parfois une trame trop tôt (866 lit 865) alors que le placement est correct : lisez le point d'entrée avecGetLeftOffset.AddMarkerconserve un marqueur par trame.ReplaceClipefface les métadonnées du clip.SetTrackEnableretourne True et ne change rien sur certaines timelines : désactivez les clips avecSetClipEnabledet confirmez sur une trame exportée.SetClipEnabledsur une timeline qui n'est pas la courante retourne False :SetCurrentTimelined'abord, puis confirmez avecGetClipEnabled.DeleteClips(items, True)ripple toutes les pistes : supprimez d'abord, sans ripple, tout ce qui croise la coupe (pistes, sous-titres).- L'isolation vocale fonctionne par script :
TimelineItem.SetVoiceIsolationState({"isEnabled": True, "amount": 100})a réduit la réverbération de la pièce de 3 à 4 dB ;Timeline.SetVoiceIsolationState(track, {...})règle un contrôle pour toute une piste. - L'API ne peut pas définir les paramètres ResolveFX (Affinage du visage, Suppression des imperfections) : une personne les règle, ou vous pilotez l'interface via l'accessibilité.
- Avec l'écran verrouillé ou Resolve minimisé, la fenêtre disparaît de l'accessibilité mais l'API continue de fonctionner : vérifiez avec des trames exportées.
- Super Scale est une propriété du media pool :
SetClipProperty("Super Scale", 2); un fichier de remplacement nécessite de la redéfinir. - Les LUT pour
Graph.SetLUTvont dans le dossier système des LUT de Resolve, dans un sous-dossier, passées par chemin relatif aprèsRefreshLUTList(). - Changer de page pendant que Resolve lit provoque un blocage ;
item.GetFusionCompByIndex(1).Execute(lua)modifie un comp sans ouvrir la page Fusion.
Importation et médias
- Un ProRes par appel d'importation : plusieurs ProRes 4444 (et une fois deux nouveaux ProRes 422) en un seul appel ont planté Resolve 21.1 (
UiMpController,std::out_of_range). Les MP4 de caméra, 22 en un appel, étaient corrects. - Après ce plantage, un processus
ResolvePythonorphelin fait que le MCPlaunch_resolvesignale Resolve comme en cours d'exécution : tuez-le et rouvrez Resolve. ImportMediaavec une liste de chemins : le formulaire avec des dictionnaires a retourné None pour MP4 sur 21.1.- Resolve met en cache le décodage par chemin : un fichier que vous rendez à nouveau reçoit un nouveau nom.
- Le PCM 24 bits little-endian dans MOV est lu comme du bruit blanc : utilisez
pcm_s24be. - L'HEVC iPhone a planté le décodeur macOS : éditez à partir de ProRes.
- L'HDR iPhone (HLG, Dolby Vision) : notre propre mappage tonal semblait délavé et a été rejeté deux fois. Demandez une exportation SDR au début.
- Un
.movd'une trame s'importe comme une image fixe de 150 trames : donnez-lui au moins 3 trames et rognez sur la timeline. - Alpha droite : Resolve lit l'alpha non prémultiplié, donc une superposition faite avec ffmpeg
overlaysur un fond transparent devient sombre ; positionnez-la avecpad=W:H:x:y:[email protected].
Rendu
- Avec
SelectAllFrames,StartRenderingretourne parfois False : définissezMarkIn/MarkOut(render.pymark), ce qui coupe aussi les queues sonores qui allongent une timeline. - Un travail H.264 créé juste après un travail audio uniquement est sorti sans vidéo : chargez un préréglage avant
SetRenderSettings, et vérifiez la présence du flux vidéo avec ffprobe. VideoQualityH.264 est un plafond, pas une moyenne : 20000 a donné 14.7 Mbps et 27300 a donné 20.7 Mbps à 1440x2560.- "Forcer le redimensionnement à la meilleure qualité" n'est pas une clé
SetRenderSettings, mais il voyage à l'intérieur d'un préréglage de rendu (ForceHighestQualitySizing). ExportRenderPreset(name, path)traitepathcomme un dossier ;ImportRenderPresetnomme le préréglage d'après le fichier.- Les préréglages stockent une fréquence d'images : définissez
FrameRateà partir de la timeline, ou une timeline à 29.97 est convertie en rendu (render.py le fait). ExportFrame(21.1.2) a donné 4 trames à 1440x2560 en 0.75 s, entre 41 et 46 dB du rendu : vérifiez les trames sans rendu.
Son et transcriptions
- Une coupe entre deux prises peut joindre un mot répété ("decide, decide") : transcrivez le mix final et cherchez les doublons. Corrigez sans changer la longueur : terminez la première pièce avant le mot, commencez la seconde plus tôt dans la pause de la bonne prise.
- La transcription n'entend pas les murmures et les souffles : les points de coupe proviennent du son de la caméra (
cut_list.py). - Scribe a placé un mot 3 s trop tôt, après un clap et un murmure : confirmez sur la forme d'onde avant de déplacer une coupe.
- Les effets sonores MP3 arrivent environ 60 ms en retard (le délai de l'encodeur n'est pas compensé) : utilisez WAV pour tout ce qui doit tomber sur une trame.
- loudnorm en un seul passage est dynamique et modifie le mix approuvé ; en deux passages, il revient silencieusement en dynamique : utilisez
loudness.py.
Séquences et plateformes
- 25 fps sur Instagram : servi à 25 (ffprobe des fichiers servis, novembre 2026). Le bégaiement provient des iPhones ProMotion, qui n'ont pas de mode 25 ou 50 Hz. Gardez les séquences 25 fps à 25 (la conversion à 30 répète une trame sur cinq) ; tournez de nouvelles séquences à 29.97 avec un obturateur 1/50.
- Le scintillement sous les lumières 50 Hz dépend de l'obturateur, pas de la fréquence d'images : 1/50 et 1/100 ne scintillent pas ; 1/60 à 30 fps oui.
- Instagram ré-encode sur le téléphone et à nouveau sur le serveur : la livraison de base est d'environ 720x1280 à 1.4 Mbps, avec un son normalisé à environ -14 LUFS, donc un master au-dessus de -14 ne gagne rien.
qc.py --simulatemontre ce qui survit. - CRF dans ffmpeg affame les fonds sombres : un reel laissé à 6.8 Mbps. Vérifiez le débit binaire réel avant de livrer.
- Une exécution sans tête
claude -ptermine son tour lorsque le rendu commence, sauf si l'invite dit d'attendre.
Scripts
| Script | Ce qu'il fait |
|---|---|
scripts/media/transcribe.py | JSON mot par mot par clip (clé via stdin, jamais dans la liste des processus) |
scripts/media/dossier.py | chaque clip sous forme de texte compact : phrases avec temps, pauses, tentatives répétées, feuilles de trame optionnelles |
scripts/media/inspect_range.py | une image d'une plage : trames, forme d'onde, mots avec temps |
scripts/media/cut_list.py | prend plan.json, transcrit, chemins des clips → cuts.json avec limites de trames alternance du zoom |
scripts/media/loudness.py | applique un gain linéaire et un limiteur, pas de loudnorm |
scripts/media/deliver.py | export propre sans timecode, métadonnées, édition ; moov first ; comparaison octet par octet |
scripts/media/qc.py | vérifie les trames, transcrit le rendu, compare avec la version de référence, simule Instagram |
scripts/resolve/run.py | connecte à Resolve, exécute un script avec resolve, project, P, __file__, imprime result JSON |
scripts/resolve/project.py | crée un projet avec résolution, fréquence d'images |
scripts/resolve/assemble.py | importe des médias, assemble la timeline à partir de cuts.json, zoom, isolation vocale |
scripts/resolve/render.py | démarre le rendu avec un préréglage, interroge l'état |
scripts/resolve/frames.py | exporte des trames depuis la timeline sans rendu |