feat(scan): binary FIX scan reads frl_/frd_ names from .aep (no AE, never hangs)
Build backend images / build content-svc (push) Failing after 15s
Build backend images / build file-svc (push) Failing after 1m51s
Build backend images / build gateway (push) Failing after 51s
Build backend images / build identity-svc (push) Failing after 57s
Build backend images / build notification-svc (push) Failing after 52s
Build backend images / build render-svc (push) Failing after 56s
Build backend images / build studio-svc (push) Failing after 57s
Build backend images / build content-svc (push) Failing after 15s
Build backend images / build file-svc (push) Failing after 1m51s
Build backend images / build gateway (push) Failing after 51s
Build backend images / build identity-svc (push) Failing after 57s
Build backend images / build notification-svc (push) Failing after 52s
Build backend images / build render-svc (push) Failing after 56s
Build backend images / build studio-svc (push) Failing after 57s
Root cause of 'stuck on AE': heavy expression-driven projects take >10min for AE to open, exceeding the scan timeout → job dies → admin UI stuck 'scanning'. Fix: extend the stdlib .aep RIFX parser to collect every Utf8 name (ParseNames), since FIX media placeholders are renamed footage ITEMS (frl_c1m1), not layers, and text are layer names (frl_c1t1) — both are Utf8 chunks. QuickScan now branches on ?mode= (or auto-detects frl_ names) and scaffolds FIX scenes/elements + frd_*color slots directly from the binary. Verified on the real final.aep that timed out in AE: 1 scene, 6 elements, 4 colors in 0.5s vs 10-min AE timeout. Admin 'Quick scan (no AE)' is now the recommended path and passes the project mode. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,9 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -86,6 +89,17 @@ func (h *ScanHandler) QuickScan(c *gin.Context) {
|
||||
c.JSON(http.StatusNotFound, models.APIError{Code: "no_template", Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// FIX / MusicVisualizer projects encode scenes in layer/item NAMES
|
||||
// (frl_c{scene}{t|m}{idx}) rather than one-comp-per-scene. Detect them by the
|
||||
// ?mode= query (or auto: any frl_c name present) and parse names instead of comps.
|
||||
mode := strings.ToLower(c.Query("mode"))
|
||||
names, nerr := aep.ParseNames(data)
|
||||
if nerr == nil && (mode == "fix" || mode == "musicvisualizer" || (mode == "" && hasFrlNames(names))) {
|
||||
c.JSON(http.StatusOK, buildFixResult(names))
|
||||
return
|
||||
}
|
||||
|
||||
comps, err := aep.ParseComps(data)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnprocessableEntity, models.APIError{Code: "parse_failed", Message: err.Error()})
|
||||
@@ -113,6 +127,74 @@ func (h *ScanHandler) QuickScan(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, res)
|
||||
}
|
||||
|
||||
var frlRe = regexp.MustCompile(`^frl_c(\d+)([tm])(\d+)$`)
|
||||
|
||||
func hasFrlNames(names []string) bool {
|
||||
for _, n := range names {
|
||||
if frlRe.MatchString(n) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// buildFixResult turns the flat list of project names into FIX-mode scenes.
|
||||
// frl_c{scene}{t|m}{idx} → element grouped by scene (t=Text, m=Media); frd_*color
|
||||
// → shared colour slot (value left empty for the binder/admin to fill, since the
|
||||
// binary parser cannot read the RGBA value).
|
||||
func buildFixResult(names []string) scanResult {
|
||||
res := scanResult{Source: "go-parser", RenderComp: "frfinal", Scenes: []scanScene{}, SharedColors: []scanColor{}}
|
||||
|
||||
byScene := map[int]*scanScene{}
|
||||
order := []int{}
|
||||
seen := map[string]bool{}
|
||||
for _, n := range names {
|
||||
m := frlRe.FindStringSubmatch(n)
|
||||
if m == nil || seen[n] {
|
||||
continue
|
||||
}
|
||||
seen[n] = true
|
||||
sceneNum, _ := strconv.Atoi(m[1])
|
||||
idx, _ := strconv.Atoi(m[3])
|
||||
sc := byScene[sceneNum]
|
||||
if sc == nil {
|
||||
sc = &scanScene{
|
||||
Key: fmt.Sprintf("c%d", sceneNum), Title: fmt.Sprintf("Scene %d", sceneNum),
|
||||
SceneType: "Normal", Elements: []interface{}{}, Colors: []scanColor{},
|
||||
}
|
||||
byScene[sceneNum] = sc
|
||||
order = append(order, sceneNum)
|
||||
}
|
||||
elemType := "Text"
|
||||
if m[2] == "m" {
|
||||
elemType = "Media"
|
||||
}
|
||||
sc.Elements = append(sc.Elements, map[string]interface{}{
|
||||
"key": n, "title": n, "type": elemType, "sort": idx,
|
||||
})
|
||||
}
|
||||
sort.Ints(order)
|
||||
for i, sn := range order {
|
||||
sc := byScene[sn]
|
||||
sc.Sort = i
|
||||
res.Scenes = append(res.Scenes, *sc)
|
||||
}
|
||||
|
||||
// shared colours — frd_*color names (controls like frd_bgstyle are skipped;
|
||||
// they are render-time switches, not scene colours).
|
||||
seenColor := map[string]bool{}
|
||||
for _, n := range names {
|
||||
if !strings.HasPrefix(n, "frd_") || !strings.Contains(strings.ToLower(n), "color") || seenColor[n] {
|
||||
continue
|
||||
}
|
||||
seenColor[n] = true
|
||||
res.SharedColors = append(res.SharedColors, scanColor{
|
||||
ElementKey: n, Title: n, AttrValue: n, Sort: len(res.SharedColors),
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// loadAep fetches the project's template .aep bytes from MinIO — directly when a
|
||||
// raw .aep was uploaded, or by extracting the .aep from the .zip bundle.
|
||||
func (h *ScanHandler) loadAep(ctx context.Context, pid uuid.UUID) ([]byte, error) {
|
||||
|
||||
Reference in New Issue
Block a user