L’encodage vidéo, distribué sur vos machines Distributed video transcoding, on your own machines
PAD Forge est un système d’encodage vidéo distribué : un orchestrateur central distribue des travaux d’encodage à une flotte de machines de post-production, en fonction de ce que chaque machine sait réellement faire. PAD Forge is a distributed video transcoding system: a central orchestrator dispatches encode work to a fleet of post-production machines, according to what each machine can actually do.
Un orchestrateur, une flotte de postes, et un routage qui sait ce que chaque machine peut réellement faire. One orchestrator, a fleet of workstations, and routing that knows what each machine can actually do.
Vue d’ensembleOverview
Ce que Forge fait What Forge does
Dans un studio, l’encodage occupe une machine à la fois pendant que les autres dorment. Forge s’installe sur celles que vous avez déjà et répartit le travail entre elles. In a facility, encoding ties up one machine at a time while the rest sit idle. Forge installs on the ones you already have and spreads the work between them.
-
N’importe quelle machine devient un agentAny machine becomes an agent
Un Mac de montage, une tour inutilisée, un poste libre le soir : on y installe un agent, il se signale au serveur, il reçoit du travail. Rien à reconfigurer quand la flotte change.An edit Mac, an idle tower, a workstation free for the evening: install an agent on it, it announces itself to the server, it gets work. Nothing to reconfigure when the fleet changes.
-
Un export, plusieurs machinesOne export, several machines
Forge découpe le travail en tâches et les distribue. Deux clips à encoder partent sur deux machines en même temps, puis Forge assemble le résultat.Forge breaks the work into tasks and hands them out. Two clips to encode go to two machines at once, then Forge assembles the result.
-
Chaque machine ne reçoit que ce qu’elle sait faireEach machine only gets what it can handle
Au démarrage, l’agent sonde sa propre machine et déclare les codecs et les formats qu’elle peut réellement traiter. Une machine sans décodeur RED n’en recevra jamais.On starting up, the agent probes its own machine and declares the codecs and formats it can actually handle. A machine with no RED decoder never receives any.
-
Les vignettes arrivent avant la finThumbnails arrive before the end
Les images fixes sont prises pendant l’encodage, pas après. Sur un rush RED de 41 minutes, la fiche a ses vignettes dans la première minute.Stills are taken while the encode runs, not after it. On a 41-minute RED rush, the item page has its thumbnails within the first minute.
-
Une machine qui s’éteint ne fait rien perdreA machine that shuts down loses nothing
Un poste éteint ou redémarré en plein travail rend sa tâche à la file, et elle repart sur une autre machine. Aucun fichier tronqué n’est laissé à la place du livrable.A workstation switched off or restarted mid-job returns its task to the queue, and it goes back out to another machine. No truncated file is left where the deliverable should be.
-
Les médias ne passent pas par le serveurMedia does not pass through the server
Les agents lisent et écrivent directement sur le NAS. Le serveur distribue le travail et suit son avancement ; il ne fait transiter aucun média.Agents read and write the NAS directly. The server hands out the work and tracks its progress; no media passes through it.
La suite entre dans le détail : comment le travail est routé, ce que l’API attend, et ce qui ne marche pas encore. What follows goes into the detail: how work is routed, what the API expects, and what does not work yet.
RoutageRouting
Une ferme d’encodage n’est pas une file d’attente An encode farm is not a queue
Une déclaration de capacité est un résultat de sonde, jamais une promesse. A capability declaration is a probe result, never a promise.
Un agent ne promet rien. En se connectant, il sonde son propre hôte et déclare ce qu’il a observé : les codecs que son ffmpeg sait produire, les formats qu’il sait ouvrir, les opérations qu’il sait exécuter, les stockages qu’il atteint réellement. An agent promises nothing. On connecting it probes its own host and declares what it observed: the codecs its ffmpeg can produce, the formats it can open, the operations it can perform, the storages it actually reaches.
Les jokers (*) sont refusés à la connexion. Un axe manquant est refusé lui aussi.
Wildcards (*) are refused at connection. A missing axis is refused too.
Vous n’écrivez jamais « envoie ça sur le Mac de montage ». Vous décrivez le travail, et il part là où il peut être fait. You never write “send that to the edit Mac”. You describe the work, and it goes where it can be done.
Un stockage qui n’est pas monté fait disparaître son identifiant à la connexion, et l’agent n’est plus servi dessus. L’erreur apparaît au démarrage, pas au milieu d’un encodage. A storage that is not mounted makes its identifier disappear at connection, and the agent is never served on it again. The error surfaces at startup, not halfway through an encode.
OpérationsOperations
Cinq opérations Five operations
Les vignettes sont produites pendant l’encodage, pas après. Sur un RED de 41 minutes, la fiche reçoit ses images dans la première minute. Stills are produced while the encode runs, not after it. On a 41-minute RED clip, the item page has its thumbnails within the first minute.
Un job porte de 1 à 10 jeux d’images : placement par every_seconds, count ou times explicites ; cadrage contain, letterbox, crop ou stretch ; sortie jpeg ou png, en fichiers séparés ou en planche contact.
A job carries 1 to 10 still sets: placement by every_seconds, count or explicit times; framing contain, letterbox, crop or stretch; jpeg or png output, as separate files or as one contact-sheet grid.
Modèle de soumissionSubmission model
Vous dites quel flux alimente quelle piste You say which stream feeds which track
Vous déclarez chaque fichier une fois, puis vous dites quel flux de quel fichier alimente quelle piste de sortie. Comme les -map de ffmpeg.
You declare each file once, then say which stream of which file feeds which output track. Like ffmpeg’s -map.
Il n’y a aucune règle implicite : le premier fichier n’est pas « la vidéo ». Rien n’est deviné. There is no implicit rule such as “the first file is the video”. Nothing is guessed.
Un job est une chaîne d’opérations ordonnée par un entier — le step — et non un graphe. Les steps 10 / 20 / 30 sont le cas courant. La requête complète est plus bas. A job is a chain of operations ordered by an integer — the step — not a graph. Steps 10 / 20 / 30 are the common case. The full request is further down.
ArchitectureArchitecture
Le média ne passe jamais par le serveur Media never passes through the server
Le média ne passe jamais par le serveur. L’orchestrateur tient l’état et distribue des références ; les agents lisent et écrivent le stockage directement. Media never passes through the server. The orchestrator holds state and dispatches references; agents read and write shared storage directly.
- Un média est adressé par identifiant de stockage et chemin relatif, jamais par un chemin absolu. C’est ce qui permet à des agents macOS, Linux et Windows de partager une même file. Media is addressed by storage identifier plus relative path, never an absolute path. That is what lets macOS, Linux and Windows agents share one queue.
- Une déclaration de capacité est un résultat de sonde, jamais une promesse. A capability declaration is a probe result, never a promise.
- Une tâche par agent à la fois. One task per agent at a time.
-
La politique de sélection tient derrière une seule couture : pour un
transcode, la déclaration la plus étroite gagne — le spécialiste ; pour toute autre opération, la plus large gagne, sinon une simple copie de flux monopoliserait la seule machine capable de décoder du RED. Selection policy sits behind a single seam: for atranscodethe narrowest declaration wins — the specialist; for any other operation the broadest wins, otherwise a plain stream copy would monopolise the only machine that can decode RED.
Une ligne pour laquelle aucun agent capable n’est connecté est garée — WAITING, parked_reason: no_capable_agent — et re-proposée dès qu’un agent capable se connecte. Une tâche garée n’est pas une tâche en échec.
A line with no capable agent connected is parked — WAITING, parked_reason: no_capable_agent — and re-offered as soon as a capable agent connects. A parked task is not a failed task.
Le décodage RED, et pourquoi il vit dans son propre processus RED decoding, and why it lives in its own process
ffmpeg annonce un démuxeur r3d mais ne porte aucun décodeur REDCODE. Le SDK de RED est une bibliothèque statique propriétaire : la lier dans un ffmpeg GPL/LGPL produirait un binaire non redistribuable. Forge met donc le décodage dans son propre processus, r3ddump, et garde un ffmpeg d’origine. La frontière de licence est la raison d’être de ce composant.
ffmpeg lists an r3d demuxer but carries no REDCODE decoder. RED’s SDK is a proprietary static library: linking it into a GPL/LGPL ffmpeg would produce a non-redistributable binary. Forge therefore puts decoding in its own process, r3ddump, and keeps a stock ffmpeg. The licence boundary is the reason this component exists.
Le SDK RED n’est pas dans le dépôt et n’y sera jamais : la licence de RED interdit d’en redistribuer les en-têtes et les bibliothèques. Un intégrateur doit en obtenir une copie auprès de RED et pointer la compilation dessus. The RED SDK is not in the repository and never will be: RED’s licence forbids redistributing its headers and libraries. An integrator must obtain a copy from RED and point the build at it.
MesuresNumbers
Mesuré, pas estimé Measured, not estimated
Les chiffres viennent de travaux réels, datés, sur du matériel nommé. Sur un même clip, le décodage RED passe de sept minutes à quarante-deux secondes dès que Metal prend la main. These figures come from real jobs, dated, on named hardware. On the same clip, RED decoding goes from seven minutes to forty-two seconds once Metal takes over.
| Ce qui est mesuréWhat | ChiffreFigure | DateWhen |
|---|---|---|
Décodage RED, multi-processus (r3ddump --workers)RED decode, multi-process (r3ddump --workers) |
~7 min | 2026-09-15 |
| Décodage RED, Metal (Apple Silicon)RED decode, Metal (Apple Silicon) | ~42 s | 2026-09-15 |
| H.264, accélération matérielle sur 4:2:0 (rush DJI réel)H.264, hardware acceleration on 4:2:0 (real DJI rush) | 254 s → 100 s | 2026-09-17 |
| Planche contact, décodage par densitéContact sheet, density-based decoding | 32,8 s → 3,8 s32.8 s → 3.8 s | 2026-09-19 |
| Images fixes sur de la 4KStills on 4K | 50 images en 5–7 s50 stills in 5–7 s | 2026-09-19 |
Aucun chiffre ne sort d’ici sans sa date et ses conditions. C’est la seule liste publiée : le projet mesure au lieu de supposer, et cinq lignes suffisent à le montrer. No figure leaves this table without its date and its conditions. It is the only published list: the project measures instead of assuming, and five rows are enough to show it.
Tolérance aux pannesFault tolerance
Une machine qui tombe ne coûte rien A machine that dies costs nothing
Le travail est tenu sous bail, pas assigné. Une machine qui s’éteint rend sa tâche à la file toute seule — et aucun fichier incomplet n’apparaît jamais au chemin final. Work is held under a lease, not assigned. A machine that goes down returns its task to the queue on its own — and no partial file ever appears at the final output path.
- Le bail expire. Une tâche n’est jamais la propriété d’une machine : elle lui est prêtée pour un temps. The lease expires. A task is never a machine’s property: it is lent to it for a while.
- La tâche retourne à la file d’elle-même, sans intervention et sans qu’un opérateur ait à constater la panne. The task returns to the queue on its own, with no intervention and with no operator having to notice the failure.
-
Rien n’est écrit au chemin final avant d’être complet : un encodage interrompu ne laisse pas un
prorestronqué là où un monteur attend un livrable. Nothing is written to the final path before it is complete: an interrupted encode does not leave a truncatedproreswhere an editor expects a deliverable.
IntégrationIntegrating
Une requête HTTP One HTTP request
Soumettre un travail, c’est un POST et un corps JSON. Vous déclarez les fichiers, vous dites quel flux alimente quelle piste, et la file s’occupe du reste.
Handing work to Forge is one POST and a JSON body. You declare the files, you say which stream feeds which track, and the queue takes it from there.
curl -X POST http://<hôte>:8000/api/tasks/ \
-H "Authorization: Bearer $PAD_FORGE_CLIENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"inputs": {"interview": {"storage": "rushes", "path": "footage/interview.mov"}},
"video": [{"v": "interview:v:0", "a": ["interview:a:0"]}],
"output_settings": {
"storage": "proxies",
"path": "interview_1080p.mp4",
"codec": "h264",
"container": "mp4",
"resolution": "1920x1080",
"bitrate": 5000
}
}'
{
"inputs": {
"v1": {"storage": "rushes", "path": "day1/clip1.mov"},
"a11": {"storage": "rushes", "path": "day1/clip1_ch1.wav"},
"a12": {"storage": "rushes", "path": "day1/clip1_ch2.wav"},
"v2": {"storage": "rushes", "path": "day1/clip2.mov"},
"a21": {"storage": "rushes", "path": "day1/clip2_ch1.wav"},
"a22": {"storage": "rushes", "path": "day1/clip2_ch2.wav"},
"mix": {"storage": "rushes", "path": "sound/mix.wav"}
},
"video": [
{"v": "v1:v:0", "a": ["a11:a:0", "a12:a:0"]},
{"v": "v2:v:0", "a": ["a21:a:0", "a22:a:0"]}
],
"audio": [["mix:a:0"]],
"output_settings": {
"storage": "proxies", "path": "out/day1.mp4",
"codec": "h264", "container": "mp4", "resolution": "1920x1080"
}
}
Les hôtes s’écrivent <hôte> ou localhost : aucune adresse réelle n’apparaît ici.
Hosts are written <hôte> or localhost: no real address appears here.
-
L’authentification est un jeton partagé porté par
Authorization: Bearer— un jeton d’agent pour la connexion WebSocket, un jeton de client pour les écritures via l’API. Une connexion WebSocket sans jeton est refusée avant qu’un état d’agent n’existe. Authentication is a shared token carried asAuthorization: Bearer— an agent token for the WebSocket connection, a client token for writes through the API. A WebSocket connection with no token is refused before any agent state exists. -
client_refest la référence du système appelant, stockée telle quelle sur chaque ligne du job. C’est la clé de relecture et d’idempotence côté intégrateur.client_refis the calling system’s own reference, stored verbatim on every row of the job. It is the read-back and idempotency key on the integrator’s side. -
Ni
bitratenicrfn’a de valeur par défaut : sans l’un des deux, c’est le défaut de l’encodeur qui s’applique. C’est une décision, pas un oubli. Neitherbitratenorcrfhas a default: without one of the two, the encoder’s own default runs. That is a decision, not an oversight. -
Le contrat complet est
docs/client-protocol.md, en français, environ 1 700 lignes, vérifié contre le code — et son client de référence est exécuté à chaquemake test. The full contract isdocs/client-protocol.md, in French, around 1,700 lines, verified against the code — and its reference client is executed on everymake test. - Forge est intégré à l’écosystème de PAD par un plugin côté MAM : Cantemo, édité par Codemill. C’est un fait côté PAD, pas une fonctionnalité supportée de Forge. Forge is integrated into PAD’s ecosystem through a plugin on the MAM side: Cantemo, published by Codemill. That is a fact on PAD’s side, not a supported Forge feature.
DéploiementDeploying
Un git push pour le serveur, un launchd par machine
One git push for the server, one launchd per machine
| CoucheLayer | TechnologieTechnology |
|---|---|
| Plan de contrôleControl plane | Python 3.11, FastAPI, SQLAlchemy 2, Alembic, PostgreSQL, WebSocket |
| Dashboard | React 18, TypeScript, MUI v7, Tailwind, Vite, react-query |
| Agent d’encodageEncode agent | Python + ffmpeg, sonde de capacité au démarrage, reprise sur checkpointPython + ffmpeg, capability probe at startup, checkpoint resume |
| Décodage REDRED decoding | r3ddump, un binaire C++ dédié lié au SDK R3D 9.x, décodage Metal sur Apple Siliconr3ddump, a dedicated C++ binary linked against the R3D 9.x SDK, Metal decoding on Apple Silicon |
| Agent AdobeAdobe agent | Panneau CEP pour Adobe Media Encoder 2025, via un pont ExtendScript — à venir, voir Où en est ForgeCEP panel for Adobe Media Encoder 2025, over an ExtendScript bridge — coming, see Project status |
| Déploiement serveurServer deployment | Dokku : une app, une image, un git push. L’API et le dashboard compilé sont servis depuis la même origine, donc il n’y a aucun CORS à configurerDokku: one app, one image, one git push. API and compiled dashboard are served from the same origin, so there is no CORS to configure |
| Déploiement agentAgent deployment | macOS natif, service launchd à l’ouverture de session, sans conteneur, lisant le NAS directementNative macOS, launchd service at login, no container, reading the NAS directly |
| Tests | Deux suites pytest (make test), plus vitest sur le frontendTwo pytest suites (make test), plus vitest on the frontend |
Les agents de production ne tournent pas dans Docker. Docker Compose est le chemin de développement : il lève l’orchestrateur, le dashboard et trois agents d’un coup. En production, l’agent est un LaunchAgent natif, parce qu’un conteneur entre l’encodeur et le NAS coûte du débit. Production agents do not run in Docker. Docker Compose is the development path: it brings up the orchestrator, the dashboard and three agents at once. In production the agent is a native LaunchAgent, because a container between the encoder and the NAS costs throughput.
État du projetProject status
Où en est Forge Project status
En production Running in production
- L’orchestrateur, déployé sur Dokku.The orchestrator, deployed on Dokku.
- Les agents ffmpeg sur les Macs de production, en services
launchd.ffmpeg agents on the production Macs, aslaunchdservices. - Le dashboard, servi par l’orchestrateur.The dashboard, served by the orchestrator.
- Le décodage RED avec accélération Metal.RED decoding with Metal acceleration.
- Les images fixes, l’assemblage multi-pistes, l’encodage segmenté.Stills, multi-track assembly, segmented encoding.
Pas là aujourd’hui Not there today
- L’agent Adobe Media Encoder ne se connecte pas. Son panneau CEP existe et fonctionne, mais sa trame de connexion est antérieure au contrôle de capacité : elle porte des jokers et il lui manque des axes, donc le serveur la refuse. Un travail est en cours pour la rendre conforme. Aujourd’hui, le
proreset le RED sont routés vers les agents ffmpeg.The Adobe Media Encoder agent does not connect. Its CEP panel exists and works, but its connection frame predates capability checking: it carries wildcards and is missing axes, so the server refuses it. A conformance effort covers this. Today,proresand RED work is routed to ffmpeg agents. - Une seule route au dashboard, la vue Jobs. La page Agents n’existe pas encore ; le rail déplie le panneau latéral.One route in the dashboard, the Jobs view. The Agents page does not exist yet; the rail unfolds the side panel.
- Desktop uniquement, à partir de 1280 px. Il n’y a pas de version mobile de l’outil.Desktop only, from 1280px up. There is no mobile version of the tool.
- macOS est la seule plateforme d’agent validée. L’architecture protège la portabilité — chemins relatifs, catalogue de stockages — mais rien n’est validé sur Windows ni sur Linux.macOS is the only validated agent platform. The architecture protects portability — relative paths, storage catalogue — but nothing is validated on Windows or Linux.
- Le frontend et le panneau AME n’ont pas encore de tests automatisés complets ; les deux paquets Python en ont.The frontend and the AME panel have no complete automated tests yet; both Python packages do.
Ouvrir le code est envisagé. Rien n’est arrêté : ni les modalités, ni le calendrier. Opening the code up is being considered. Nothing is settled: neither the terms nor the timing.