@aicopycoders/exodus 2026.9.2000-beta.5 → 2026.9.2000-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/commands/video.d.ts
CHANGED
|
@@ -8,10 +8,31 @@ export interface ClipWord {
|
|
|
8
8
|
s: number;
|
|
9
9
|
e: number;
|
|
10
10
|
}
|
|
11
|
+
export type ClipQcTakeNeighbours = {
|
|
12
|
+
state: "not-applicable";
|
|
13
|
+
} | {
|
|
14
|
+
state: "not-attached";
|
|
15
|
+
} | {
|
|
16
|
+
state: "none-accepted";
|
|
17
|
+
} | {
|
|
18
|
+
state: "attached";
|
|
19
|
+
scenes: number[];
|
|
20
|
+
};
|
|
21
|
+
export interface ClipQcTake {
|
|
22
|
+
kind: "take" | "soft-retry";
|
|
23
|
+
failCodes: string[];
|
|
24
|
+
warnCodes: string[];
|
|
25
|
+
neighbours: ClipQcTakeNeighbours;
|
|
26
|
+
judgeWording?: {
|
|
27
|
+
code: string;
|
|
28
|
+
wording: string;
|
|
29
|
+
}[];
|
|
30
|
+
}
|
|
11
31
|
export interface ClipQc {
|
|
12
32
|
verdict: "pass" | "fail";
|
|
13
33
|
attempts: number;
|
|
14
34
|
neighbours?: number[];
|
|
35
|
+
takes?: ClipQcTake[];
|
|
15
36
|
}
|
|
16
37
|
export interface ClipFinding {
|
|
17
38
|
check: string;
|
|
@@ -21,6 +42,9 @@ export interface ClipFinding {
|
|
|
21
42
|
judgeDetail?: string;
|
|
22
43
|
judgeSeverity?: "fail" | "warn";
|
|
23
44
|
}
|
|
45
|
+
export declare function qcWithoutJudgeWording(qc: ClipQc): ClipQc;
|
|
46
|
+
export declare const NO_TAKE_HISTORY_LINE = "no history recorded for this clip";
|
|
47
|
+
export declare function renderQcTakeHistory(takes: ClipQcTake[] | undefined): string[];
|
|
24
48
|
export type ArtifactSubset = {
|
|
25
49
|
type: "storyboard";
|
|
26
50
|
storyboard?: unknown;
|
|
@@ -132,6 +156,10 @@ export interface NodeItem {
|
|
|
132
156
|
take: number;
|
|
133
157
|
outcome: string;
|
|
134
158
|
label: string;
|
|
159
|
+
displacedHistory?: {
|
|
160
|
+
title: string;
|
|
161
|
+
lines: string[];
|
|
162
|
+
};
|
|
135
163
|
};
|
|
136
164
|
artifact?: ArtifactSubset;
|
|
137
165
|
}
|
|
@@ -277,7 +305,7 @@ export interface VideoManifest {
|
|
|
277
305
|
failed: PullFailure[];
|
|
278
306
|
provenance?: RunProvenance;
|
|
279
307
|
}
|
|
280
|
-
export declare const CAST_LEDGER_BASE
|
|
308
|
+
export declare const CAST_LEDGER_BASE: number;
|
|
281
309
|
export interface PullPlan {
|
|
282
310
|
downloads: PullDownload[];
|
|
283
311
|
texts: PullTextFile[];
|
package/dist/commands/video.js
CHANGED
|
@@ -6,6 +6,7 @@ import { displayRunStatus, formatApiError } from "../lib/format.js";
|
|
|
6
6
|
import { missingRouteLine } from "../lib/route-support.js";
|
|
7
7
|
import { hasBinary } from "../lib/preflight.js";
|
|
8
8
|
import { ASSET_UPLOAD_POLICY, pauseAheadLine, } from "./workflow.js";
|
|
9
|
+
import { HELPER_LEDGER_BASE, SET_OPTION_LEDGER_BASE } from "../lib/helperLedgerBase.js";
|
|
9
10
|
export const helpText = `
|
|
10
11
|
exodus video — make a video ad from a saved workflow, pull every piece, upload your cut
|
|
11
12
|
|
|
@@ -149,6 +150,48 @@ const ASSETS_PATH = "/api/v2/workflows/assets";
|
|
|
149
150
|
function withoutJudgeFields({ judgeDetail: _d, judgeSeverity: _s, ...rest }) {
|
|
150
151
|
return rest;
|
|
151
152
|
}
|
|
153
|
+
export function qcWithoutJudgeWording(qc) {
|
|
154
|
+
if (!qc.takes)
|
|
155
|
+
return qc;
|
|
156
|
+
return {
|
|
157
|
+
...qc,
|
|
158
|
+
takes: qc.takes.map(({ judgeWording: _w, ...rest }) => rest),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
export const NO_TAKE_HISTORY_LINE = "no history recorded for this clip";
|
|
162
|
+
const TAKE_NEIGHBOUR_SENTENCE = {
|
|
163
|
+
"not-applicable": () => "never compared",
|
|
164
|
+
"not-attached": () => "no neighbours attached",
|
|
165
|
+
"none-accepted": () => "no accepted neighbours yet",
|
|
166
|
+
attached: ({ scenes }) => `compared against scene${scenes.length === 1 ? "" : "s"} ${scenes.join(", ")}`,
|
|
167
|
+
};
|
|
168
|
+
export function renderQcTakeHistory(takes) {
|
|
169
|
+
if (!takes || takes.length === 0)
|
|
170
|
+
return [NO_TAKE_HISTORY_LINE];
|
|
171
|
+
const lines = [];
|
|
172
|
+
takes.forEach((take, index) => {
|
|
173
|
+
const say = TAKE_NEIGHBOUR_SENTENCE[take.neighbours.state];
|
|
174
|
+
const parts = [
|
|
175
|
+
take.failCodes.length ? `failed ${take.failCodes.join(", ")}` : "passed",
|
|
176
|
+
...(take.warnCodes.length ? [`warned ${take.warnCodes.join(", ")}`] : []),
|
|
177
|
+
say(take.neighbours),
|
|
178
|
+
];
|
|
179
|
+
const bonus = take.kind === "soft-retry" ? " (bonus take)" : "";
|
|
180
|
+
lines.push(`take ${index + 1}${bonus}: ${parts.join("; ")}`);
|
|
181
|
+
for (const { code, wording } of take.judgeWording ?? []) {
|
|
182
|
+
lines.push(` checker said (${code}): ${wording}`);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
return lines;
|
|
186
|
+
}
|
|
187
|
+
function printDisplacedHistory(lines, lastRedo) {
|
|
188
|
+
const history = lastRedo.displacedHistory;
|
|
189
|
+
if (!history)
|
|
190
|
+
return;
|
|
191
|
+
lines.push(` ${history.title}`);
|
|
192
|
+
for (const line of history.lines)
|
|
193
|
+
lines.push(` ${line}`);
|
|
194
|
+
}
|
|
152
195
|
export function asVideoRun(data) {
|
|
153
196
|
const run = (data ?? {});
|
|
154
197
|
return { ...run, nodes: Array.isArray(run.nodes) ? run.nodes : [] };
|
|
@@ -363,6 +406,13 @@ export function stopLines(stop, runId, runUrl) {
|
|
|
363
406
|
}
|
|
364
407
|
if (stop.at === "failed") {
|
|
365
408
|
if (stop.repair) {
|
|
409
|
+
if (!stop.step) {
|
|
410
|
+
return [
|
|
411
|
+
"This run stopped at a step that can't be picked back up from here.",
|
|
412
|
+
"Start a fresh run to get this ad made.",
|
|
413
|
+
`Open the run: ${runUrl}`,
|
|
414
|
+
];
|
|
415
|
+
}
|
|
366
416
|
return [
|
|
367
417
|
`${stepName(stop.step)} failed${stop.error ? `: ${stop.error}` : "."}`,
|
|
368
418
|
`Try that step again: exodus workflow repair ${runId} retry`,
|
|
@@ -412,7 +462,10 @@ export function planFailureLines(node, runId) {
|
|
|
412
462
|
}
|
|
413
463
|
return lines;
|
|
414
464
|
}
|
|
415
|
-
export const CAST_LEDGER_BASE =
|
|
465
|
+
export const CAST_LEDGER_BASE = HELPER_LEDGER_BASE + 10000;
|
|
466
|
+
function isPullSceneIndex(sceneIndex) {
|
|
467
|
+
return sceneIndex >= 0 && sceneIndex < HELPER_LEDGER_BASE;
|
|
468
|
+
}
|
|
416
469
|
const FALLBACK_EXT = {
|
|
417
470
|
image: "png",
|
|
418
471
|
video: "mp4",
|
|
@@ -429,6 +482,10 @@ function extFor(url, family) {
|
|
|
429
482
|
export function scenePrefix(sceneIndex) {
|
|
430
483
|
return `scene-${String(sceneIndex).padStart(2, "0")}`;
|
|
431
484
|
}
|
|
485
|
+
function clipQcOf(item) {
|
|
486
|
+
const artifact = item?.artifact;
|
|
487
|
+
return artifact && artifact.type === "video" ? artifact.qc?.takes : undefined;
|
|
488
|
+
}
|
|
432
489
|
function outputsOfNodeKind(run, kind) {
|
|
433
490
|
return run.nodes.filter((n) => n.kind === kind).flatMap((n) => n.outputs ?? []);
|
|
434
491
|
}
|
|
@@ -458,7 +515,9 @@ function keyframeDownload(sceneIndex, imageUrl) {
|
|
|
458
515
|
function reservedCastFrames(items) {
|
|
459
516
|
const byIndex = new Map();
|
|
460
517
|
for (const item of items) {
|
|
461
|
-
if (item.itemKind === "frame" &&
|
|
518
|
+
if (item.itemKind === "frame" &&
|
|
519
|
+
item.sceneIndex >= CAST_LEDGER_BASE &&
|
|
520
|
+
item.sceneIndex < SET_OPTION_LEDGER_BASE) {
|
|
462
521
|
byIndex.set(item.sceneIndex, item);
|
|
463
522
|
}
|
|
464
523
|
}
|
|
@@ -611,6 +670,8 @@ export function planPull(run, items, opts) {
|
|
|
611
670
|
for (const frame of artifact.frames ?? []) {
|
|
612
671
|
if (!frame.imageUrl)
|
|
613
672
|
continue;
|
|
673
|
+
if (!isPullSceneIndex(frame.sceneIndex))
|
|
674
|
+
continue;
|
|
614
675
|
keyframeByScene.set(frame.sceneIndex, keyframeDownload(frame.sceneIndex, frame.imageUrl));
|
|
615
676
|
}
|
|
616
677
|
}
|
|
@@ -628,7 +689,7 @@ export function planPull(run, items, opts) {
|
|
|
628
689
|
};
|
|
629
690
|
continue;
|
|
630
691
|
}
|
|
631
|
-
if (typeof artifact.sceneIndex !== "number" || artifact.sceneIndex
|
|
692
|
+
if (typeof artifact.sceneIndex !== "number" || !isPullSceneIndex(artifact.sceneIndex))
|
|
632
693
|
continue;
|
|
633
694
|
voiceByScene.set(artifact.sceneIndex, {
|
|
634
695
|
download: {
|
|
@@ -669,6 +730,8 @@ export function planPull(run, items, opts) {
|
|
|
669
730
|
}
|
|
670
731
|
if (artifact.type !== "video" || typeof artifact.sceneIndex !== "number")
|
|
671
732
|
continue;
|
|
733
|
+
if (!isPullSceneIndex(artifact.sceneIndex))
|
|
734
|
+
continue;
|
|
672
735
|
const clip = clipFromArtifact(artifact.sceneIndex, artifact);
|
|
673
736
|
if (!clip)
|
|
674
737
|
continue;
|
|
@@ -678,6 +741,8 @@ export function planPull(run, items, opts) {
|
|
|
678
741
|
const frameItemByScene = new Map();
|
|
679
742
|
const sceneFrameNodeIds = new Set(run.nodes.filter((n) => n.kind === "scene-frames").map((n) => n.nodeId));
|
|
680
743
|
for (const item of items) {
|
|
744
|
+
if (!isPullSceneIndex(item.sceneIndex))
|
|
745
|
+
continue;
|
|
681
746
|
if (item.itemKind === "clip")
|
|
682
747
|
clipItemByScene.set(item.sceneIndex, item);
|
|
683
748
|
if (item.itemKind === "frame" && sceneFrameNodeIds.has(item.nodeId)) {
|
|
@@ -709,7 +774,7 @@ export function planPull(run, items, opts) {
|
|
|
709
774
|
...frameItemByScene.keys(),
|
|
710
775
|
]),
|
|
711
776
|
]
|
|
712
|
-
.filter((sceneIndex) => sceneIndex
|
|
777
|
+
.filter((sceneIndex) => isPullSceneIndex(sceneIndex))
|
|
713
778
|
.sort((a, b) => a - b);
|
|
714
779
|
const scenes = sceneIndexes.map((sceneIndex) => {
|
|
715
780
|
const clip = clipByScene.get(sceneIndex);
|
|
@@ -744,7 +809,7 @@ export function planPull(run, items, opts) {
|
|
|
744
809
|
: "this-file",
|
|
745
810
|
voice: voice?.download.file ?? null,
|
|
746
811
|
keyframe: keyframeByScene.get(sceneIndex)?.file ?? null,
|
|
747
|
-
qc: clip?.qc
|
|
812
|
+
qc: clip?.qc ? qcWithoutJudgeWording(clip.qc) : null,
|
|
748
813
|
revoiced: clip ? clip.revoiced : null,
|
|
749
814
|
speechTrimmed: clip ? clip.speechTrimmed : null,
|
|
750
815
|
rawStorageId: clip ? clip.rawStorageId : null,
|
|
@@ -1088,10 +1153,19 @@ export async function statusFlow(runId, json, deps) {
|
|
|
1088
1153
|
for (const sceneIndex of [...byScene.keys()].sort((a, b) => a - b)) {
|
|
1089
1154
|
const row = byScene.get(sceneIndex);
|
|
1090
1155
|
lines.push(`${String(sceneIndex).padEnd(5)} ${itemWord(row.clip).padEnd(8)} ${itemWord(row.voiceover).padEnd(8)} ${itemWord(row.frame)}`);
|
|
1091
|
-
if (row.clip?.lastRedo)
|
|
1156
|
+
if (row.clip?.lastRedo) {
|
|
1092
1157
|
lines.push(` clip: ${row.clip.lastRedo.label}`);
|
|
1093
|
-
|
|
1158
|
+
printDisplacedHistory(lines, row.clip.lastRedo);
|
|
1159
|
+
}
|
|
1160
|
+
if (row.clip?.lastRedo || row.clip?.flagged) {
|
|
1161
|
+
for (const line of renderQcTakeHistory(clipQcOf(row.clip))) {
|
|
1162
|
+
lines.push(` ${line}`);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
if (row.voiceover?.lastRedo) {
|
|
1094
1166
|
lines.push(` voice: ${row.voiceover.lastRedo.label}`);
|
|
1167
|
+
printDisplacedHistory(lines, row.voiceover.lastRedo);
|
|
1168
|
+
}
|
|
1095
1169
|
if (row.frame?.lastRedo)
|
|
1096
1170
|
lines.push(` picture: ${row.frame.lastRedo.label}`);
|
|
1097
1171
|
for (const finding of row.clip?.findings ?? []) {
|