feat(render B2): render binder writes user edits into AE before render
Build backend images / build content-svc (push) Failing after 52s
Build backend images / build file-svc (push) Failing after 56s
Build backend images / build gateway (push) Failing after 53s
Build backend images / build identity-svc (push) Failing after 1m29s
Build backend images / build notification-svc (push) Failing after 1m38s
Build backend images / build render-svc (push) Failing after 1m53s
Build backend images / build studio-svc (push) Failing after 56s

Edits previously never reached the MP4 (the node rendered template defaults). Now:
- render-svc claim includes the saved input values as bindings (GetRenderBindings →
  saved_scene_contents with non-empty value).
- node-agent: new binder.go emits a JSON bind-spec + downloads media locally, runs the
  pre-existing data-driven bind.jsx via afterfx (sets text layers' Source Text, replaces
  media footage), saves a bound.aep next to the template, then aerender renders THAT.
- 12-min timeout + fresh-AE + done-marker polling (mirrors scan). Non-fatal: on bind
  failure the job still renders template defaults.

Verified binding data flows (edited frl_c1t1/frl_c1t2 → claim bindings). Live MP4
verification needs the updated node-agent.exe re-run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-07 01:22:20 +03:30
parent a69bc62724
commit 47a4ced973
8 changed files with 280 additions and 0 deletions
@@ -37,6 +37,18 @@ type Job struct {
// CompName is the composition to render (-comp), e.g. "frfinal". When empty the
// node renders the project's render queue (-rqindex 1) instead.
CompName string
// AfterFxPath is afterfx.exe (the AE app, scriptable) — used by the binder to write
// input values into the project before rendering. Render itself uses aerender.exe.
AfterFxPath string
// Bindings are the user's edited input values to write into the .aep before render.
Bindings []Binding
}
// Binding is one input value written into the AE project before render.
type Binding struct {
Key string // AE layer/footage name, e.g. frl_c1t1 / frl_c1m1
Type string // content element type (Text, Media, …)
Value string // text content, or a media URL
}
// Run executes the render job, calling onProgress and onPreview as it advances.
@@ -58,6 +70,20 @@ func Run(ctx context.Context, aePath, workDir string, job *Job, onProgress Progr
}
return mockRender(ctx, job, outputPath, onProgress, onPreview)
}
// Render binder: write the user's edited input values into the project before
// rendering so the MP4 reflects their text/media. Non-fatal — on failure we render
// the template defaults rather than failing the job.
if len(job.Bindings) > 0 && job.AfterFxPath != "" {
_ = onProgress(ctx, 6, "Applying your edits…")
bound, berr := RunBinder(ctx, job, workDir)
if berr != nil {
log.Printf("[job %s] binder failed (%v) — rendering template defaults", job.JobID, berr)
} else {
job.AEPFilePath = bound // render the bound project with the user's values
}
}
return aeRender(ctx, aePath, job, outputPath, onProgress, onPreview)
}