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 }