fix(scan): Fix-mode scanner + dialog suppression + cancel/timer + importer revive
Build backend images / build content-svc (push) Failing after 1m25s
Build backend images / build file-svc (push) Failing after 1m10s
Build backend images / build gateway (push) Failing after 56s
Build backend images / build identity-svc (push) Failing after 53s
Build backend images / build notification-svc (push) Failing after 57s
Build backend images / build render-svc (push) Failing after 48s
Build backend images / build studio-svc (push) Failing after 1m5s
Build backend images / build content-svc (push) Failing after 1m25s
Build backend images / build file-svc (push) Failing after 1m10s
Build backend images / build gateway (push) Failing after 56s
Build backend images / build identity-svc (push) Failing after 53s
Build backend images / build notification-svc (push) Failing after 57s
Build backend images / build render-svc (push) Failing after 48s
Build backend images / build studio-svc (push) Failing after 1m5s
- scan.jsx: app.beginSuppressDialogs() + clean quit (no AE hang on font/footage dialogs); FIX-mode branch parses frl_c(x)t/m(y) layer names → scenes by c(x); flexible/mockup keep comp-based walk; FR_SCAN_MODE selects. - render-svc: scan job carries project mode; cancel endpoint + node watchdog that kills AE on cancel; parseObjectURL handles minio:// (bucket in host); scan with no template fails cleanly; status guards so late results can't un-cancel. - content importer: revive soft-deleted scenes instead of duplicate-inserting (fixes scenes_project_id_key unique violation); orphan diff ignores deleted. - admin: scan dialog gets project-type picker + elapsed timer + Cancel button. - node-agent: AE-2026 wiring (host port 5010, host-reachable presign endpoint), FR_SCAN_MODE plumbing. docs/aep-template-convention.md: per-type naming + bundles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
|
||||
var aepPath = getenv("FR_SCAN_AEP");
|
||||
var outPath = getenv("FR_SCAN_OUT") || (Folder.temp.fsName + "/fr_scan.json");
|
||||
var mode = String(getenv("FR_SCAN_MODE") || "flexible").toLowerCase(); // fix | flexible | mockup | musicvisualizer
|
||||
|
||||
// ── minimal JSON serializer (older AE has no JSON.stringify) ──────────────
|
||||
function esc(s) {
|
||||
@@ -138,29 +139,32 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
function run() {
|
||||
if (aepPath) app.open(new File(aepPath));
|
||||
var proj = app.project;
|
||||
var result = { source: "ae-jsx", render_comp: null, scenes: [], shared_colors: [] };
|
||||
// ── frshare colours (shared by all modes) ──────────────────────────────────
|
||||
function readSharedColors(proj) {
|
||||
var colors = [];
|
||||
for (var i = 1; i <= proj.numItems; i++) {
|
||||
var item = proj.item(i);
|
||||
if (!(item instanceof CompItem) || item.name !== "frshare") continue;
|
||||
for (var j = 1; j <= item.numLayers; j++) {
|
||||
var cl = item.layer(j), ct = readText(cl);
|
||||
if (ct && isColor(ct.text)) {
|
||||
colors.push({ element_key: cl.name, title: cl.name, attr_value: "fill", default_color: normColor(ct.text), sort: j });
|
||||
}
|
||||
}
|
||||
}
|
||||
return colors;
|
||||
}
|
||||
|
||||
// ── FLEXIBLE / Mockup — each scene IS a comp; layers frl_/frd_ inside ───────
|
||||
function scanFlexible(proj) {
|
||||
var result = { source: "ae-jsx", render_comp: null, scenes: [], shared_colors: readSharedColors(proj) };
|
||||
for (var i = 1; i <= proj.numItems; i++) {
|
||||
var item = proj.item(i);
|
||||
if (!(item instanceof CompItem)) continue;
|
||||
var nm = item.name;
|
||||
|
||||
if (nm === "frfinal") { result.render_comp = "frfinal"; continue; }
|
||||
if (nm === "frshare") {
|
||||
for (var j = 1; j <= item.numLayers; j++) {
|
||||
var cl = item.layer(j), ct = readText(cl);
|
||||
result.shared_colors.push({
|
||||
element_key: cl.name, title: cl.name, attr_value: "fill",
|
||||
default_color: (ct && isColor(ct.text)) ? normColor(ct.text) : "#000000", sort: j
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (nm === "frfinal" || nm === "flatrender") { result.render_comp = nm; continue; }
|
||||
if (nm === "frshare") continue;
|
||||
if (!compHasEditable(item)) continue;
|
||||
|
||||
var s = scanScene(item);
|
||||
result.scenes.push({
|
||||
key: nm, title: nm, scene_type: "Normal",
|
||||
@@ -168,6 +172,52 @@
|
||||
sort: result.scenes.length, elements: s.elements, colors: s.colors
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── FIX / MusicVisualizer — scenes encoded in layer names frl_c(x)t/m(y) ────
|
||||
function scanFix(proj) {
|
||||
var result = { source: "ae-jsx", render_comp: "frfinal", scenes: [], shared_colors: readSharedColors(proj) };
|
||||
var sceneMap = {}, order = [];
|
||||
var re = /^frl_c(\d+)([tm])(\d+)$/;
|
||||
for (var i = 1; i <= proj.numItems; i++) {
|
||||
var item = proj.item(i);
|
||||
if (!(item instanceof CompItem) || item.name === "frshare") continue;
|
||||
for (var l = 1; l <= item.numLayers; l++) {
|
||||
var layer = item.layer(l), m = String(layer.name).match(re);
|
||||
if (!m) continue;
|
||||
var sc = parseInt(m[1], 10), typ = m[2], idx = parseInt(m[3], 10);
|
||||
if (!sceneMap[sc]) {
|
||||
sceneMap[sc] = { key: "c" + sc, title: "Scene " + sc, scene_type: "Normal", sort: sc, elements: [], colors: [] };
|
||||
order.push(sc);
|
||||
}
|
||||
var el = { key: layer.name, title: layer.name, sort: idx };
|
||||
if (typ === "t") {
|
||||
var txt = readText(layer);
|
||||
el.type = "Text";
|
||||
if (txt) { el.default_value = txt.text; el.font_face = txt.font; el.font_size = txt.fontSize; el.justify = txt.justify; }
|
||||
} else {
|
||||
el.type = "Media";
|
||||
var src = null; try { src = layer.source; } catch (e) {}
|
||||
if (src) { try { el.width = src.width; el.height = src.height; } catch (e) {} try { el.video_support = !!src.hasVideo; } catch (e) {} }
|
||||
}
|
||||
sceneMap[sc].elements.push(el);
|
||||
}
|
||||
}
|
||||
order.sort(function (a, b) { return a - b; });
|
||||
for (var k = 0; k < order.length; k++) { var s = sceneMap[order[k]]; s.sort = k; result.scenes.push(s); }
|
||||
return result;
|
||||
}
|
||||
|
||||
function run() {
|
||||
// Silence ALL AE dialogs (missing fonts/footage, version prompts, alerts) so a
|
||||
// real project never hangs the headless scan waiting for a click.
|
||||
try { app.beginSuppressDialogs(); } catch (e) {}
|
||||
try { app.preferences.savePrefAsLong("Misc Section", "Play sound when render finishes", 0, PREFType.PREF_Type_MACHINE_INDEPENDENT); } catch (e) {}
|
||||
if (aepPath) app.open(new File(aepPath));
|
||||
var proj = app.project;
|
||||
|
||||
var result = (mode === "fix" || mode === "musicvisualizer") ? scanFix(proj) : scanFlexible(proj);
|
||||
|
||||
var out = new File(outPath);
|
||||
out.encoding = "UTF-8";
|
||||
@@ -181,6 +231,13 @@
|
||||
} catch (e) {
|
||||
try { var ef = new File(outPath + ".error"); ef.open("w"); ef.write("scan error: " + e.toString()); ef.close(); } catch (e2) {}
|
||||
}
|
||||
// Quit AE so the headless afterfx process returns (set FR_SCAN_QUIT=0 to keep open while debugging).
|
||||
try { if (getenv("FR_SCAN_QUIT") !== "0") app.quit(); } catch (e) {}
|
||||
try { app.endSuppressDialogs(false); } catch (e) {}
|
||||
// Quit AE without a save prompt so the headless afterfx process returns
|
||||
// (set FR_SCAN_QUIT=0 to keep it open while debugging).
|
||||
try {
|
||||
if (getenv("FR_SCAN_QUIT") !== "0") {
|
||||
try { app.project.close(CloseOptions.DO_NOT_SAVE_CHANGES); } catch (e2) {}
|
||||
app.quit();
|
||||
}
|
||||
} catch (e) {}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user