6cf8716d7e
Build backend images / build content-svc (push) Failing after 13s
Build backend images / build file-svc (push) Failing after 53s
Build backend images / build gateway (push) Failing after 1m22s
Build backend images / build identity-svc (push) Failing after 19s
Build backend images / build notification-svc (push) Failing after 21s
Build backend images / build render-svc (push) Failing after 20s
Build backend images / build studio-svc (push) Failing after 1m6s
C2 — real-AE scene snapshots on the node:
- node-agent: runner/snapshot.go RunSnapshot (aerender -comp <key> -s f -e f
→ findRenderedOutput → ffmpeg -frames:v 1 PNG); client ClaimSnapshot /
GetSnapshotUploadURL / ReportSnapshotResult / ReportSnapshotFail; snapshotLoop +
pollSnapshotOnce mirroring the scan loop (reuses the AE-exclusive lock).
- render-svc: GetSnapshotJobMeta + UploadURL handler presigns a PUT to the
public-read user-uploads bucket at snapshots/{project}/{scene}.png and returns a
permanent public_url (not the time-limited export presign); MINIO_UPLOAD_BUCKET +
MINIO_PUBLIC_URL config + compose env + /snapshot/:id/upload-url route.
Epic B — bind edited colours into the render:
- render-svc GetRenderBindings UNIONs studio.saved_shared_colors +
saved_scene_colors (type 'color') so the node writes them before render.
- node-agent binder.go routes type:"color" bindings into the bind-spec colors[]
array that bind.jsx already applies to the frshare colour layers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
70 lines
2.2 KiB
Go
70 lines
2.2 KiB
Go
package runner
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
)
|
|
|
|
// RunSnapshot renders a single frame of compName from aepPath and writes a PNG
|
|
// still, returning its path. It reuses the render pipeline shape: aerender emits
|
|
// the comp's output module (lossless AVI/MOV) for one frame, then ffmpeg extracts
|
|
// a single PNG. Requires aerender (aePath) and ffmpeg on the node.
|
|
func RunSnapshot(ctx context.Context, aePath, aepPath, compName string, frame int, workDir string) (string, error) {
|
|
if aePath == "" {
|
|
return "", fmt.Errorf("AE path required for snapshot render")
|
|
}
|
|
if compName == "" {
|
|
return "", fmt.Errorf("comp name required for snapshot render")
|
|
}
|
|
if err := os.MkdirAll(workDir, 0o755); err != nil {
|
|
return "", fmt.Errorf("workdir: %w", err)
|
|
}
|
|
out := filepath.Join(workDir, "snap.avi")
|
|
_ = os.Remove(out)
|
|
|
|
// -s/-e bound the render to a single frame; aerender writes via the comp's
|
|
// output module (cmd.Dir = project folder so relative footage resolves).
|
|
args := []string{
|
|
"-project", aepPath, "-comp", compName,
|
|
"-s", strconv.Itoa(frame), "-e", strconv.Itoa(frame),
|
|
"-output", out,
|
|
}
|
|
log.Printf("[snapshot] aerender %v", args)
|
|
cmd := exec.CommandContext(ctx, aePath, args...)
|
|
cmd.Dir = filepath.Dir(aepPath)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return "", fmt.Errorf("aerender: %w", err)
|
|
}
|
|
actual := findRenderedOutput(out)
|
|
if actual == "" {
|
|
return "", fmt.Errorf("aerender produced no output for comp %q", compName)
|
|
}
|
|
|
|
ff := ffmpegPath()
|
|
if ff == "" {
|
|
return "", fmt.Errorf("ffmpeg not found (set FFMPEG_PATH or place ffmpeg.exe next to the agent)")
|
|
}
|
|
png := filepath.Join(workDir, "snap.png")
|
|
_ = os.Remove(png)
|
|
ffArgs := []string{"-y", "-i", actual, "-frames:v", "1", png}
|
|
log.Printf("[snapshot] ffmpeg %v", ffArgs)
|
|
fc := exec.CommandContext(ctx, ff, ffArgs...)
|
|
fc.Stdout = os.Stdout
|
|
fc.Stderr = os.Stderr
|
|
if err := fc.Run(); err != nil {
|
|
return "", fmt.Errorf("ffmpeg still: %w", err)
|
|
}
|
|
_ = os.Remove(actual) // drop the intermediate render
|
|
if st, err := os.Stat(png); err != nil || st.Size() == 0 {
|
|
return "", fmt.Errorf("no snapshot image produced")
|
|
}
|
|
return png, nil
|
|
}
|