Files
slogan/styleagent/agent/render.go
T
2026-08-03 10:29:31 +08:00

86 lines
2.3 KiB
Go

package agent
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/gogf/gf/v2/frame/g"
)
const (
renderFramesCount = 36
renderFrameSize = "256x512"
renderTimeout = 15 * time.Minute
)
// RenderAvatarFrames 将化身 GLB 预渲染为绕 Y 轴旋转帧序列,返回帧目录访问 URL。
// 帧目录已就绪直接复用;render.enabled=false 或渲染失败时返回 error,由调用方降级。
func RenderAvatarFrames(ctx context.Context, glb string, outKey string) (framesURL string, err error) {
if !g.Cfg().MustGet(ctx, "render.enabled", true).Bool() {
return "", errors.New("3D 渲染服务未启用")
}
dir := filepath.Join("workspace", "avatar_frames", outKey)
if framesReady(dir) {
return "/workspace/avatar_frames/" + outKey, nil
}
if _, err := os.Stat(glb); err != nil {
return "", fmt.Errorf("化身 GLB 不存在: %w", err)
}
if err := runRender(ctx, glb, dir); err != nil {
return "", err
}
return "/workspace/avatar_frames/" + outKey, nil
}
func framesReady(dir string) bool {
entries, err := os.ReadDir(dir)
if err != nil {
return false
}
count := 0
for _, e := range entries {
if strings.HasPrefix(e.Name(), "frame_") && strings.HasSuffix(e.Name(), ".png") {
count++
}
}
return count >= renderFramesCount
}
func nodeBin(ctx context.Context) string {
bin := g.Cfg().MustGet(ctx, "render.node_bin", "node").String()
if bin == "" {
return "node"
}
// 配置路径不存在(如容器环境)→ 回退 PATH 中的 node
if _, err := os.Stat(bin); err != nil {
return "node"
}
return bin
}
func runRender(ctx context.Context, glb, out string) error {
if err := os.MkdirAll(out, 0o755); err != nil {
return err
}
return execNode(ctx, filepath.Join("scripts", "avatar-render", "render.js"),
"--glb", glb, "--out", out,
"--frames", fmt.Sprint(renderFramesCount), "--size", renderFrameSize)
}
func execNode(ctx context.Context, script string, args ...string) error {
cmdCtx, cancel := context.WithTimeout(ctx, renderTimeout)
defer cancel()
cmd := exec.CommandContext(cmdCtx, nodeBin(ctx), append([]string{script}, args...)...)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("node 渲染失败: %v: %s", err, strings.TrimSpace(string(out)))
}
return nil
}