1ff6e494c0
Build backend images / build content-svc (push) Failing after 19s
Build backend images / build file-svc (push) Failing after 1m53s
Build backend images / build gateway (push) Failing after 16s
Build backend images / build identity-svc (push) Failing after 7m1s
Build backend images / build notification-svc (push) Failing after 7m24s
Build backend images / build render-svc (push) Failing after 3m12s
Build backend images / build studio-svc (push) Failing after 43s
feat: AE template scanner + scene editor + AEP bundle pipeline
Scene editor (admin): per-project Scenes / Shared Colors / Color Presets
manager (ProjectScenes) reachable from each project.
AEP bundle pipeline: upload .aep or .zip → stored once per template at
templates/{project_id}/(bundle.zip|template.aep); render claim probes and
returns is_bundle+md5; node-agent extracts the bundle, locates the .aep
(zip-slip guarded), and caches by md5 so repeated renders extract once.
AE template scanner ("read scenes/colours/configs from the AEP"):
- content-svc importer: POST /v1/projects/{id}/scan/{preview,apply} —
review-diff-then-merge into scenes/elements/colours (manual edits kept).
- render-svc Go quick-scan: stdlib RIFX parser extracts comp names+durations
(no AE) → POST /v1/template-scans/{id}/quick.
- render-svc AE scan jobs + node-agent runner: queue → node runs scan.jsx
(reverse of legacy JSXGenerator conventions: frfinal/frshare/frl_/frd_) →
posts ScanResult back. Migration 26_render_scan_jobs.
- admin UI: "اسکن از افترافکت" with quick/full engines + diff-review modal.
Verified: importer preview/apply, Go quick-scan end-to-end (synthetic .aep →
scene imported), bundle extract unit tests, RIFX parser unit tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
68 lines
2.0 KiB
Go
68 lines
2.0 KiB
Go
package runner
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
//go:embed scan.jsx
|
|
var scanScript []byte
|
|
|
|
// WriteScanScript writes the embedded scanner JSX into workDir and returns its path.
|
|
func WriteScanScript(workDir string) (string, error) {
|
|
dir := filepath.Join(workDir, "scripts")
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
return "", err
|
|
}
|
|
p := filepath.Join(dir, "scan.jsx")
|
|
if err := os.WriteFile(p, scanScript, 0o644); err != nil {
|
|
return "", err
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// RunScan runs the After Effects template scanner against aepPath and returns the
|
|
// JSON the script writes to outPath. afterfxPath must be afterfx.exe (the AE app,
|
|
// not aerender.exe — only the app can run ScriptUI/ExtendScript).
|
|
//
|
|
// afterfx -r runs the script and the script calls app.quit(); we still poll for the
|
|
// output file because afterfx can return before the file is flushed.
|
|
func RunScan(ctx context.Context, afterfxPath, aepPath, workDir, outPath string) ([]byte, error) {
|
|
scriptPath, err := WriteScanScript(workDir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("write scan script: %w", err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(outPath), 0o755); err != nil {
|
|
return nil, err
|
|
}
|
|
_ = os.Remove(outPath)
|
|
_ = os.Remove(outPath + ".error")
|
|
|
|
cmd := exec.CommandContext(ctx, afterfxPath, "-r", scriptPath)
|
|
cmd.Env = append(os.Environ(), "FR_SCAN_AEP="+aepPath, "FR_SCAN_OUT="+outPath)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
// afterfx may exit non-zero on app.quit() — don't treat the exit code as fatal;
|
|
// success is determined by the presence of the output JSON file.
|
|
_ = cmd.Run()
|
|
|
|
for {
|
|
if eb, rerr := os.ReadFile(outPath + ".error"); rerr == nil && len(eb) > 0 {
|
|
return nil, fmt.Errorf("scan script error: %s", string(eb))
|
|
}
|
|
if b, rerr := os.ReadFile(outPath); rerr == nil && len(b) > 0 {
|
|
return b, nil
|
|
}
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, fmt.Errorf("scan timed out waiting for %s", outPath)
|
|
case <-time.After(2 * time.Second):
|
|
}
|
|
}
|
|
}
|