Files
flatrender/services/node-agent/internal/runner/snapshot.go
T

70 lines
2.2 KiB
Go
Raw Normal View History

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
}