feat(render): node-agent AE snapshot runner (Epic C2) + colour render-binding (Epic B)
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
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>
This commit is contained in:
@@ -341,6 +341,95 @@ func (c *Client) ScanStatus(ctx context.Context, scanJobID string) (string, erro
|
||||
return out.Status, nil
|
||||
}
|
||||
|
||||
// ── Scene snapshots ─────────────────────────────────────────────────────────
|
||||
|
||||
// SnapshotClaim is returned when a per-scene snapshot job is claimed.
|
||||
type SnapshotClaim struct {
|
||||
SnapshotJobID string `json:"snapshot_job_id"`
|
||||
ProjectID string `json:"project_id"`
|
||||
SceneID string `json:"scene_id"`
|
||||
SceneKey string `json:"scene_key"`
|
||||
CompName string `json:"comp_name"`
|
||||
Frame int `json:"frame"`
|
||||
AEPDownloadURL string `json:"aep_download_url"`
|
||||
IsBundle bool `json:"is_bundle"`
|
||||
BundleMD5 string `json:"bundle_md5"`
|
||||
}
|
||||
|
||||
// SnapshotUploadURLResponse carries the presigned PUT + the permanent public URL.
|
||||
type SnapshotUploadURLResponse struct {
|
||||
UploadURL string `json:"upload_url"`
|
||||
ObjectKey string `json:"object_key"`
|
||||
PublicURL string `json:"public_url"`
|
||||
}
|
||||
|
||||
// ClaimSnapshot atomically claims the next queued snapshot job (204 → nil,nil).
|
||||
func (c *Client) ClaimSnapshot(ctx context.Context, nodeID, region string) (*SnapshotClaim, error) {
|
||||
resp, err := c.do(ctx, http.MethodPost, "/v1/internal/snapshot/claim",
|
||||
ClaimJobRequest{NodeID: nodeID, Region: region})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusNoContent {
|
||||
return nil, nil
|
||||
}
|
||||
if resp.StatusCode >= 300 {
|
||||
return nil, fmt.Errorf("snapshot claim: HTTP %d", resp.StatusCode)
|
||||
}
|
||||
var sc SnapshotClaim
|
||||
if err := json.NewDecoder(resp.Body).Decode(&sc); err != nil {
|
||||
return nil, fmt.Errorf("snapshot claim decode: %w", err)
|
||||
}
|
||||
return &sc, nil
|
||||
}
|
||||
|
||||
// GetSnapshotUploadURL asks the orchestrator for a presigned PUT + public URL.
|
||||
func (c *Client) GetSnapshotUploadURL(ctx context.Context, jobID string) (*SnapshotUploadURLResponse, error) {
|
||||
resp, err := c.do(ctx, http.MethodPost,
|
||||
fmt.Sprintf("/v1/internal/snapshot/%s/upload-url", jobID), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 300 {
|
||||
return nil, fmt.Errorf("snapshot upload-url: HTTP %d", resp.StatusCode)
|
||||
}
|
||||
var out SnapshotUploadURLResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
||||
return nil, fmt.Errorf("decode: %w", err)
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// ReportSnapshotResult posts the uploaded still's public URL.
|
||||
func (c *Client) ReportSnapshotResult(ctx context.Context, jobID, imageURL string) error {
|
||||
resp, err := c.do(ctx, http.MethodPost,
|
||||
fmt.Sprintf("/v1/internal/snapshot/%s/result", jobID), map[string]string{"image_url": imageURL})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("snapshot result: HTTP %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReportSnapshotFail marks a snapshot job as failed.
|
||||
func (c *Client) ReportSnapshotFail(ctx context.Context, jobID, reason string) error {
|
||||
resp, err := c.do(ctx, http.MethodPost,
|
||||
fmt.Sprintf("/v1/internal/snapshot/%s/fail", jobID), FailRequest{Reason: reason})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("snapshot fail: HTTP %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdatePreview sends a base64-encoded preview frame to the orchestrator.
|
||||
// Errors are non-fatal — the UI simply won't update the preview image.
|
||||
func (c *Client) UpdatePreview(ctx context.Context, jobID, imageB64 string) error {
|
||||
|
||||
Reference in New Issue
Block a user