@skyphusion-labs/vivijure-core 0.9.0

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.
Files changed (267) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +87 -0
  3. package/dist/audio-routing.d.ts +2 -0
  4. package/dist/audio-routing.d.ts.map +1 -0
  5. package/dist/audio-routing.js +4 -0
  6. package/dist/audio-routing.js.map +1 -0
  7. package/dist/audio-stage.d.ts +4 -0
  8. package/dist/audio-stage.d.ts.map +1 -0
  9. package/dist/audio-stage.js +25 -0
  10. package/dist/audio-stage.js.map +1 -0
  11. package/dist/beat-analyze.d.ts +22 -0
  12. package/dist/beat-analyze.d.ts.map +1 -0
  13. package/dist/beat-analyze.js +111 -0
  14. package/dist/beat-analyze.js.map +1 -0
  15. package/dist/beat-sync-types.d.ts +33 -0
  16. package/dist/beat-sync-types.d.ts.map +1 -0
  17. package/dist/beat-sync-types.js +4 -0
  18. package/dist/beat-sync-types.js.map +1 -0
  19. package/dist/bundle-assembler.d.ts +54 -0
  20. package/dist/bundle-assembler.d.ts.map +1 -0
  21. package/dist/bundle-assembler.js +376 -0
  22. package/dist/bundle-assembler.js.map +1 -0
  23. package/dist/bundle-durations.d.ts +5 -0
  24. package/dist/bundle-durations.d.ts.map +1 -0
  25. package/dist/bundle-durations.js +68 -0
  26. package/dist/bundle-durations.js.map +1 -0
  27. package/dist/bundle-storyboard.d.ts +11 -0
  28. package/dist/bundle-storyboard.d.ts.map +1 -0
  29. package/dist/bundle-storyboard.js +82 -0
  30. package/dist/bundle-storyboard.js.map +1 -0
  31. package/dist/captions.d.ts +25 -0
  32. package/dist/captions.d.ts.map +1 -0
  33. package/dist/captions.js +56 -0
  34. package/dist/captions.js.map +1 -0
  35. package/dist/cast-db.d.ts +61 -0
  36. package/dist/cast-db.d.ts.map +1 -0
  37. package/dist/cast-db.js +324 -0
  38. package/dist/cast-db.js.map +1 -0
  39. package/dist/cast-lora-train.d.ts +18 -0
  40. package/dist/cast-lora-train.d.ts.map +1 -0
  41. package/dist/cast-lora-train.js +215 -0
  42. package/dist/cast-lora-train.js.map +1 -0
  43. package/dist/cast-loras.d.ts +21 -0
  44. package/dist/cast-loras.d.ts.map +1 -0
  45. package/dist/cast-loras.js +69 -0
  46. package/dist/cast-loras.js.map +1 -0
  47. package/dist/clip-content-validate.d.ts +43 -0
  48. package/dist/clip-content-validate.d.ts.map +1 -0
  49. package/dist/clip-content-validate.js +115 -0
  50. package/dist/clip-content-validate.js.map +1 -0
  51. package/dist/clip-job-model.d.ts +42 -0
  52. package/dist/clip-job-model.d.ts.map +1 -0
  53. package/dist/clip-job-model.js +8 -0
  54. package/dist/clip-job-model.js.map +1 -0
  55. package/dist/clip-validate.d.ts +33 -0
  56. package/dist/clip-validate.d.ts.map +1 -0
  57. package/dist/clip-validate.js +247 -0
  58. package/dist/clip-validate.js.map +1 -0
  59. package/dist/d1-retry.d.ts +20 -0
  60. package/dist/d1-retry.d.ts.map +1 -0
  61. package/dist/d1-retry.js +67 -0
  62. package/dist/d1-retry.js.map +1 -0
  63. package/dist/db-env.d.ts +6 -0
  64. package/dist/db-env.d.ts.map +1 -0
  65. package/dist/db-env.js +2 -0
  66. package/dist/db-env.js.map +1 -0
  67. package/dist/dialogue-lines.d.ts +23 -0
  68. package/dist/dialogue-lines.d.ts.map +1 -0
  69. package/dist/dialogue-lines.js +84 -0
  70. package/dist/dialogue-lines.js.map +1 -0
  71. package/dist/film-advance-lease.d.ts +9 -0
  72. package/dist/film-advance-lease.d.ts.map +1 -0
  73. package/dist/film-advance-lease.js +19 -0
  74. package/dist/film-advance-lease.js.map +1 -0
  75. package/dist/film-model.d.ts +415 -0
  76. package/dist/film-model.d.ts.map +1 -0
  77. package/dist/film-model.js +541 -0
  78. package/dist/film-model.js.map +1 -0
  79. package/dist/film-orchestrator.d.ts +216 -0
  80. package/dist/film-orchestrator.d.ts.map +1 -0
  81. package/dist/film-orchestrator.js +2204 -0
  82. package/dist/film-orchestrator.js.map +1 -0
  83. package/dist/film-render-bridge.d.ts +26 -0
  84. package/dist/film-render-bridge.d.ts.map +1 -0
  85. package/dist/film-render-bridge.js +176 -0
  86. package/dist/film-render-bridge.js.map +1 -0
  87. package/dist/finish-hash.d.ts +10 -0
  88. package/dist/finish-hash.d.ts.map +1 -0
  89. package/dist/finish-hash.js +60 -0
  90. package/dist/finish-hash.js.map +1 -0
  91. package/dist/index.d.ts +48 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +45 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/key-safety.d.ts +5 -0
  96. package/dist/key-safety.d.ts.map +1 -0
  97. package/dist/key-safety.js +23 -0
  98. package/dist/key-safety.js.map +1 -0
  99. package/dist/lora-bundle.d.ts +19 -0
  100. package/dist/lora-bundle.d.ts.map +1 -0
  101. package/dist/lora-bundle.js +75 -0
  102. package/dist/lora-bundle.js.map +1 -0
  103. package/dist/lora-keys.d.ts +2 -0
  104. package/dist/lora-keys.d.ts.map +1 -0
  105. package/dist/lora-keys.js +4 -0
  106. package/dist/lora-keys.js.map +1 -0
  107. package/dist/modules/conformance.d.ts +58 -0
  108. package/dist/modules/conformance.d.ts.map +1 -0
  109. package/dist/modules/conformance.js +369 -0
  110. package/dist/modules/conformance.js.map +1 -0
  111. package/dist/modules/manifest-validate.d.ts +5 -0
  112. package/dist/modules/manifest-validate.d.ts.map +1 -0
  113. package/dist/modules/manifest-validate.js +60 -0
  114. package/dist/modules/manifest-validate.js.map +1 -0
  115. package/dist/modules/registry.d.ts +184 -0
  116. package/dist/modules/registry.d.ts.map +1 -0
  117. package/dist/modules/registry.js +653 -0
  118. package/dist/modules/registry.js.map +1 -0
  119. package/dist/modules/render-pipeline.d.ts +38 -0
  120. package/dist/modules/render-pipeline.d.ts.map +1 -0
  121. package/dist/modules/render-pipeline.js +40 -0
  122. package/dist/modules/render-pipeline.js.map +1 -0
  123. package/dist/modules/types.d.ts +544 -0
  124. package/dist/modules/types.d.ts.map +1 -0
  125. package/dist/modules/types.js +57 -0
  126. package/dist/modules/types.js.map +1 -0
  127. package/dist/operator-config.d.ts +20 -0
  128. package/dist/operator-config.d.ts.map +1 -0
  129. package/dist/operator-config.js +85 -0
  130. package/dist/operator-config.js.map +1 -0
  131. package/dist/output-extract.d.ts +8 -0
  132. package/dist/output-extract.d.ts.map +1 -0
  133. package/dist/output-extract.js +129 -0
  134. package/dist/output-extract.js.map +1 -0
  135. package/dist/planner-prompt.d.ts +12 -0
  136. package/dist/planner-prompt.d.ts.map +1 -0
  137. package/dist/planner-prompt.js +337 -0
  138. package/dist/planner-prompt.js.map +1 -0
  139. package/dist/planner-yaml.d.ts +15 -0
  140. package/dist/planner-yaml.d.ts.map +1 -0
  141. package/dist/planner-yaml.js +157 -0
  142. package/dist/planner-yaml.js.map +1 -0
  143. package/dist/platform/fetcher.d.ts +3 -0
  144. package/dist/platform/fetcher.d.ts.map +1 -0
  145. package/dist/platform/fetcher.js +7 -0
  146. package/dist/platform/fetcher.js.map +1 -0
  147. package/dist/platform/index.d.ts +6 -0
  148. package/dist/platform/index.d.ts.map +1 -0
  149. package/dist/platform/index.js +5 -0
  150. package/dist/platform/index.js.map +1 -0
  151. package/dist/platform/object-store-r2.d.ts +26 -0
  152. package/dist/platform/object-store-r2.d.ts.map +1 -0
  153. package/dist/platform/object-store-r2.js +74 -0
  154. package/dist/platform/object-store-r2.js.map +1 -0
  155. package/dist/platform/orchestrator-context.d.ts +20 -0
  156. package/dist/platform/orchestrator-context.d.ts.map +1 -0
  157. package/dist/platform/orchestrator-context.js +28 -0
  158. package/dist/platform/orchestrator-context.js.map +1 -0
  159. package/dist/platform/r2-types.d.ts +41 -0
  160. package/dist/platform/r2-types.d.ts.map +1 -0
  161. package/dist/platform/r2-types.js +3 -0
  162. package/dist/platform/r2-types.js.map +1 -0
  163. package/dist/platform/types.d.ts +90 -0
  164. package/dist/platform/types.d.ts.map +1 -0
  165. package/dist/platform/types.js +23 -0
  166. package/dist/platform/types.js.map +1 -0
  167. package/dist/preflight.d.ts +57 -0
  168. package/dist/preflight.d.ts.map +1 -0
  169. package/dist/preflight.js +243 -0
  170. package/dist/preflight.js.map +1 -0
  171. package/dist/presign.d.ts +5 -0
  172. package/dist/presign.d.ts.map +1 -0
  173. package/dist/presign.js +24 -0
  174. package/dist/presign.js.map +1 -0
  175. package/dist/public-id.d.ts +8 -0
  176. package/dist/public-id.d.ts.map +1 -0
  177. package/dist/public-id.js +25 -0
  178. package/dist/public-id.js.map +1 -0
  179. package/dist/render-adopt.d.ts +3 -0
  180. package/dist/render-adopt.d.ts.map +1 -0
  181. package/dist/render-adopt.js +92 -0
  182. package/dist/render-adopt.js.map +1 -0
  183. package/dist/render-log.d.ts +25 -0
  184. package/dist/render-log.d.ts.map +1 -0
  185. package/dist/render-log.js +139 -0
  186. package/dist/render-log.js.map +1 -0
  187. package/dist/render-module-config.d.ts +28 -0
  188. package/dist/render-module-config.d.ts.map +1 -0
  189. package/dist/render-module-config.js +124 -0
  190. package/dist/render-module-config.js.map +1 -0
  191. package/dist/render-mux.d.ts +18 -0
  192. package/dist/render-mux.d.ts.map +1 -0
  193. package/dist/render-mux.js +69 -0
  194. package/dist/render-mux.js.map +1 -0
  195. package/dist/render-orchestrator.d.ts +71 -0
  196. package/dist/render-orchestrator.d.ts.map +1 -0
  197. package/dist/render-orchestrator.js +376 -0
  198. package/dist/render-orchestrator.js.map +1 -0
  199. package/dist/render-sweep.d.ts +5 -0
  200. package/dist/render-sweep.d.ts.map +1 -0
  201. package/dist/render-sweep.js +62 -0
  202. package/dist/render-sweep.js.map +1 -0
  203. package/dist/renders-db.d.ts +129 -0
  204. package/dist/renders-db.d.ts.map +1 -0
  205. package/dist/renders-db.js +752 -0
  206. package/dist/renders-db.js.map +1 -0
  207. package/dist/runpod-submit.d.ts +151 -0
  208. package/dist/runpod-submit.d.ts.map +1 -0
  209. package/dist/runpod-submit.js +469 -0
  210. package/dist/runpod-submit.js.map +1 -0
  211. package/dist/runpod-types.d.ts +2 -0
  212. package/dist/runpod-types.d.ts.map +1 -0
  213. package/dist/runpod-types.js +4 -0
  214. package/dist/runpod-types.js.map +1 -0
  215. package/dist/scatter-notify.d.ts +4 -0
  216. package/dist/scatter-notify.d.ts.map +1 -0
  217. package/dist/scatter-notify.js +43 -0
  218. package/dist/scatter-notify.js.map +1 -0
  219. package/dist/scatter-orchestrator-types.d.ts +56 -0
  220. package/dist/scatter-orchestrator-types.d.ts.map +1 -0
  221. package/dist/scatter-orchestrator-types.js +3 -0
  222. package/dist/scatter-orchestrator-types.js.map +1 -0
  223. package/dist/scatter-orchestrator.d.ts +70 -0
  224. package/dist/scatter-orchestrator.d.ts.map +1 -0
  225. package/dist/scatter-orchestrator.js +614 -0
  226. package/dist/scatter-orchestrator.js.map +1 -0
  227. package/dist/scatter.d.ts +35 -0
  228. package/dist/scatter.d.ts.map +1 -0
  229. package/dist/scatter.js +171 -0
  230. package/dist/scatter.js.map +1 -0
  231. package/dist/secret-store.d.ts +5 -0
  232. package/dist/secret-store.d.ts.map +1 -0
  233. package/dist/secret-store.js +19 -0
  234. package/dist/secret-store.js.map +1 -0
  235. package/dist/shot-durations-parse.d.ts +3 -0
  236. package/dist/shot-durations-parse.d.ts.map +1 -0
  237. package/dist/shot-durations-parse.js +43 -0
  238. package/dist/shot-durations-parse.js.map +1 -0
  239. package/dist/srt.d.ts +11 -0
  240. package/dist/srt.d.ts.map +1 -0
  241. package/dist/srt.js +54 -0
  242. package/dist/srt.js.map +1 -0
  243. package/dist/storyboard-ids.d.ts +2 -0
  244. package/dist/storyboard-ids.d.ts.map +1 -0
  245. package/dist/storyboard-ids.js +12 -0
  246. package/dist/storyboard-ids.js.map +1 -0
  247. package/dist/storyboard-projects-db.d.ts +31 -0
  248. package/dist/storyboard-projects-db.d.ts.map +1 -0
  249. package/dist/storyboard-projects-db.js +153 -0
  250. package/dist/storyboard-projects-db.js.map +1 -0
  251. package/dist/storyboard-validate.d.ts +72 -0
  252. package/dist/storyboard-validate.d.ts.map +1 -0
  253. package/dist/storyboard-validate.js +595 -0
  254. package/dist/storyboard-validate.js.map +1 -0
  255. package/dist/structured-events.d.ts +6 -0
  256. package/dist/structured-events.d.ts.map +1 -0
  257. package/dist/structured-events.js +15 -0
  258. package/dist/structured-events.js.map +1 -0
  259. package/dist/tar.d.ts +9 -0
  260. package/dist/tar.d.ts.map +1 -0
  261. package/dist/tar.js +151 -0
  262. package/dist/tar.js.map +1 -0
  263. package/dist/voices.d.ts +12 -0
  264. package/dist/voices.d.ts.map +1 -0
  265. package/dist/voices.js +37 -0
  266. package/dist/voices.js.map +1 -0
  267. package/package.json +71 -0
@@ -0,0 +1,215 @@
1
+ // Cast LoRA training: submit a single-slot bundle, poll RunPod, harvest the key.
2
+ import { getCastById, toPublicCast, markLoraFailed, markLoraReady, setLoraJob, } from "./cast-db.js";
3
+ import { assembleBundle } from "./bundle-assembler.js";
4
+ import { pollRenderJob, submitTrainLoraJob } from "./runpod-submit.js";
5
+ import { buildLoraTrainingBundleArgs, deriveLoraDestKey, extractTrainedLoraKey, } from "./lora-bundle.js";
6
+ const MIN_TRAINING_REFS = 4;
7
+ // --- Stuck-training reconciler (#295) ---------------------------------------------------------
8
+ // A cast LoRA training row transitions off `training` only when a poll observes a TERMINAL RunPod
9
+ // status. If the backing job ages out of RunPod's retention window before any poll catches a terminal
10
+ // state, the poll keeps returning not-found/non-terminal and the row wedges in `training` forever --
11
+ // and the train-lora route then 409s, so the character can never be retrained without a manual D1
12
+ // edit. The reconciler closes that hole: a not-found (404) past a grace window, or a row older than a
13
+ // hard ceiling, is force-failed (an HONEST degrade with a clear lora_error, never a silent reset) so
14
+ // the user can re-fire.
15
+ // Ignore a 404 within this window of the row's last write: a just-submitted job can briefly 404 before
16
+ // RunPod registers it (mirrors the GC-grace discipline on the status path).
17
+ export const LORA_TRAIN_404_GRACE_SECONDS = 120;
18
+ // Hard ceiling: cast training is ~10-15 min, so a row sitting in `training` past this was never
19
+ // observed terminal and is treated as failed regardless of the poll (backstop for a vanished job).
20
+ export const LORA_TRAIN_MAX_AGE_SECONDS = 60 * 60;
21
+ // SQLite `datetime('now')` returns "YYYY-MM-DD HH:MM:SS" in UTC with no zone. Parse it as a UTC epoch
22
+ // in ms; returns null for a missing/unparseable value (the caller then declines to reconcile, never
23
+ // false-failing a row whose age it cannot establish).
24
+ export function sqliteUtcToMs(s) {
25
+ if (!s)
26
+ return null;
27
+ const t = s.includes("T") ? s : s.replace(" ", "T");
28
+ const withZone = /([zZ]|[+-]\d\d:?\d\d)$/.test(t) ? t : t + "Z";
29
+ const ms = Date.parse(withZone);
30
+ return Number.isFinite(ms) ? ms : null;
31
+ }
32
+ // How long a row has sat in `training`, measured from its last write (updated_at -- set by setLoraJob
33
+ // when the job was submitted). null when the timestamp can't be parsed.
34
+ export function trainingAgeSeconds(cast, now) {
35
+ const ms = sqliteUtcToMs(cast.updated_at);
36
+ if (ms === null)
37
+ return null;
38
+ return (now - ms) / 1000;
39
+ }
40
+ // Pure decision: should a `training` row whose backing job we just polled be force-failed? `poll` is
41
+ // the pollRenderJob result; ageSeconds is trainingAgeSeconds (null => unknown, never reconcile). A 404
42
+ // past the grace window means the job is gone from RunPod retention; the max-age ceiling is the
43
+ // backstop for a job that simply never reported terminal.
44
+ export function decideStuckTraining(poll, ageSeconds) {
45
+ if (ageSeconds === null)
46
+ return { reconcile: false };
47
+ const notFound = poll.ok === false && poll.status === 404;
48
+ if (notFound && ageSeconds >= LORA_TRAIN_404_GRACE_SECONDS) {
49
+ return {
50
+ reconcile: true,
51
+ reason: `backing RunPod job not found (HTTP 404; aged out of retention) after ` +
52
+ `${Math.round(ageSeconds)}s in training -- it cannot complete; re-fire training`,
53
+ };
54
+ }
55
+ if (ageSeconds >= LORA_TRAIN_MAX_AGE_SECONDS) {
56
+ return {
57
+ reconcile: true,
58
+ reason: `training exceeded max age (${Math.round(ageSeconds)}s >= ${LORA_TRAIN_MAX_AGE_SECONDS}s); ` +
59
+ `backing job not observed terminal -- re-fire training`,
60
+ };
61
+ }
62
+ return { reconcile: false };
63
+ }
64
+ export async function refreshTrainingLora(env, cast, now = Date.now()) {
65
+ if (!cast || cast.lora_status !== "training" || !cast.lora_job_id)
66
+ return cast;
67
+ const ageSeconds = trainingAgeSeconds(cast, now);
68
+ let poll;
69
+ try {
70
+ poll = await pollRenderJob(env, cast.lora_job_id);
71
+ }
72
+ catch {
73
+ poll = { ok: false, error: "poll threw" };
74
+ }
75
+ if (poll.ok) {
76
+ const view = poll.view;
77
+ if (view.status === "COMPLETED") {
78
+ const loraKey = extractTrainedLoraKey(view.output);
79
+ if (loraKey)
80
+ return (await markLoraReady(env, cast.id, loraKey)) || cast;
81
+ return ((await markLoraFailed(env, cast.id, "GPU job completed but envelope did not include lora_key")) || cast);
82
+ }
83
+ if (view.status === "FAILED" ||
84
+ view.status === "TIMED_OUT" ||
85
+ view.status === "CANCELLED") {
86
+ return ((await markLoraFailed(env, cast.id, view.error || `training ${view.status.toLowerCase()}`)) || cast);
87
+ }
88
+ }
89
+ // Not terminal (poll 404 / transport error / a non-terminal-but-wedged view): reconcile a row whose
90
+ // backing job is gone past the grace window, or that has simply aged out. #295 -- never leave it
91
+ // wedged in `training` (that 409s every retry).
92
+ const decision = decideStuckTraining(poll, ageSeconds);
93
+ if (decision.reconcile) {
94
+ return (await markLoraFailed(env, cast.id, decision.reason)) || cast;
95
+ }
96
+ return cast;
97
+ }
98
+ export async function handleCastTrainLora(request, env, id) {
99
+ let bodyRenderOverrides;
100
+ try {
101
+ const ct = (request.headers.get("content-type") || "").toLowerCase();
102
+ if (ct.includes("application/json")) {
103
+ const parsed = (await request.json());
104
+ if (parsed?.renderOverrides &&
105
+ typeof parsed.renderOverrides === "object" &&
106
+ !Array.isArray(parsed.renderOverrides)) {
107
+ bodyRenderOverrides = parsed.renderOverrides;
108
+ }
109
+ }
110
+ }
111
+ catch {
112
+ /* empty body is fine */
113
+ }
114
+ const cast = await getCastById(env, id);
115
+ if (!cast)
116
+ return json({ error: "cast not found" }, 404);
117
+ if (cast.lora_status === "training") {
118
+ return json({
119
+ error: "a LoRA training job is already in flight for this cast member",
120
+ jobId: cast.lora_job_id,
121
+ }, 409);
122
+ }
123
+ if (!cast.portrait_key) {
124
+ return json({ error: "cast member needs a portrait before training (set one via /cast)" }, 400);
125
+ }
126
+ if (cast.ref_keys.length < MIN_TRAINING_REFS) {
127
+ return json({
128
+ error: `cast member has only ${cast.ref_keys.length} training refs; need at least ${MIN_TRAINING_REFS}. Use the training-set generator on /cast.`,
129
+ }, 400);
130
+ }
131
+ const timestamp = Math.floor(Date.now() / 1000);
132
+ const args = buildLoraTrainingBundleArgs(cast, String(timestamp));
133
+ let bundleResult;
134
+ try {
135
+ bundleResult = await assembleBundle(env, args);
136
+ }
137
+ catch (err) {
138
+ const m = err instanceof Error ? err.message : String(err);
139
+ return json({ error: `bundle assembly failed: ${m}` }, 500);
140
+ }
141
+ if (!bundleResult.ok) {
142
+ return json({ error: "bundle assembly failed", details: bundleResult.errors }, 500);
143
+ }
144
+ const loraDestKey = deriveLoraDestKey(cast.id, timestamp);
145
+ const submit = await submitTrainLoraJob(env, {
146
+ project: args.storyboard.projectName,
147
+ bundleKey: bundleResult.bundleKey,
148
+ renderOverrides: bodyRenderOverrides,
149
+ });
150
+ if (!submit.ok) {
151
+ return json({ error: submit.error }, 502);
152
+ }
153
+ const updated = await setLoraJob(env, cast.id, submit.view.jobId);
154
+ return json({
155
+ ok: true,
156
+ jobId: submit.view.jobId,
157
+ status: submit.view.status,
158
+ statusRaw: submit.view.statusRaw,
159
+ bundleKey: bundleResult.bundleKey,
160
+ loraDestKey,
161
+ cast: toPublicCast(updated || cast),
162
+ });
163
+ }
164
+ export async function handleCastLoraStatus(env, id) {
165
+ const cast = await getCastById(env, id);
166
+ if (!cast)
167
+ return json({ error: "cast not found" }, 404);
168
+ if (!cast.lora_job_id) {
169
+ return json({ cast: toPublicCast(cast), view: null });
170
+ }
171
+ const ageSeconds = trainingAgeSeconds(cast, Date.now());
172
+ let poll;
173
+ try {
174
+ poll = await pollRenderJob(env, cast.lora_job_id);
175
+ }
176
+ catch {
177
+ poll = { ok: false, error: "poll threw" };
178
+ }
179
+ if (poll.ok) {
180
+ const view = poll.view;
181
+ if (view.status === "COMPLETED") {
182
+ const loraKey = extractTrainedLoraKey(view.output);
183
+ if (!loraKey) {
184
+ const updated = await markLoraFailed(env, cast.id, "GPU job completed but envelope did not include lora_key");
185
+ return json({ cast: toPublicCast(updated || cast), view });
186
+ }
187
+ const updated = await markLoraReady(env, cast.id, loraKey);
188
+ return json({ cast: toPublicCast(updated || cast), view });
189
+ }
190
+ if (view.status === "FAILED" || view.status === "TIMED_OUT" || view.status === "CANCELLED") {
191
+ const msg = view.error || `training ${view.status.toLowerCase()}`;
192
+ const updated = await markLoraFailed(env, cast.id, msg);
193
+ return json({ cast: toPublicCast(updated || cast), view });
194
+ }
195
+ return json({ cast: toPublicCast(cast), view });
196
+ }
197
+ // poll failed (404 / transport): reconcile a wedged `training` row (#295) before surfacing the
198
+ // error, so an aged-out job can't keep the row stuck (which 409s every retry). A 404 inside the
199
+ // grace window, or any non-training row, falls through to the honest 502.
200
+ if (cast.lora_status === "training") {
201
+ const decision = decideStuckTraining(poll, ageSeconds);
202
+ if (decision.reconcile) {
203
+ const updated = await markLoraFailed(env, cast.id, decision.reason);
204
+ return json({ cast: toPublicCast(updated || cast), view: null, reconciled: true });
205
+ }
206
+ }
207
+ return json({ error: poll.error, cast: toPublicCast(cast) }, 502);
208
+ }
209
+ function json(body, status = 200) {
210
+ return new Response(JSON.stringify(body), {
211
+ status,
212
+ headers: { "content-type": "application/json; charset=utf-8" },
213
+ });
214
+ }
215
+ //# sourceMappingURL=cast-lora-train.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cast-lora-train.js","sourceRoot":"","sources":["../src/cast-lora-train.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { Env } from "./platform/orchestrator-context.js";
2
+ export interface ResolvedCastLoras {
3
+ pretrained: Record<string, string>;
4
+ voices: Record<string, string>;
5
+ castIds: Record<string, number>;
6
+ skipped: string[];
7
+ skippedDetail: SkippedCast[];
8
+ }
9
+ export interface SkippedCast {
10
+ slot: string;
11
+ name?: string;
12
+ reason: "not a valid cast id" | "cast member not found" | "LoRA still training" | "no trained LoRA";
13
+ }
14
+ /** Map slot -> cast_id from the request body into slot -> loras/ R2 key (drops non-ready rows),
15
+ * slot -> voice_id (kept for every resolvable cast row), and slot -> cast_id (every well-formed
16
+ * entry, used to bank a freshly-trained adapter back onto the cast member). */
17
+ export declare function resolveCastLoras(env: Env, castLoras: Record<string, unknown> | undefined): Promise<ResolvedCastLoras>;
18
+ /** Build an actionable, per-character rejection message from the skipped slots: name who needs
19
+ * training (falling back to the slot id when the cast row did not resolve) and where to do it. */
20
+ export declare function untrainedCastMessage(skippedDetail: SkippedCast[]): string;
21
+ //# sourceMappingURL=cast-loras.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cast-loras.d.ts","sourceRoot":"","sources":["../src/cast-loras.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,oCAAoC,CAAC;AAM9D,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAInC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAI/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAKhC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,EAAE,WAAW,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IAIb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,qBAAqB,GAAG,uBAAuB,GAAG,qBAAqB,GAAG,iBAAiB,CAAC;CACrG;AAED;;gFAEgF;AAChF,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC7C,OAAO,CAAC,iBAAiB,CAAC,CA6C5B;AAED;mGACmG;AACnG,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,WAAW,EAAE,GAAG,MAAM,CAMzE"}
@@ -0,0 +1,69 @@
1
+ // Resolve planner castLoras ({ slot: <opaque cast public id> }) into pretrained LoRA R2 keys for the
2
+ // GPU backend, and
3
+ // (riding the same cast-row fetch) the per-slot dialogue voice. The studio is single-user, so the cast
4
+ // lookup is not really identity-scoped; voice comes free off the row we already read for the LoRA.
5
+ import { getCastById, getCastIdByPublicId } from "./cast-db.js";
6
+ import { isPublicId } from "./public-id.js";
7
+ import { refreshTrainingLora } from "./cast-lora-train.js";
8
+ import { coerceVoiceId, DEFAULT_VOICE_ID } from "./voices.js";
9
+ /** Map slot -> cast_id from the request body into slot -> loras/ R2 key (drops non-ready rows),
10
+ * slot -> voice_id (kept for every resolvable cast row), and slot -> cast_id (every well-formed
11
+ * entry, used to bank a freshly-trained adapter back onto the cast member). */
12
+ export async function resolveCastLoras(env, castLoras) {
13
+ const pretrained = {};
14
+ const voices = {};
15
+ const castIds = {};
16
+ const skipped = [];
17
+ const skippedDetail = [];
18
+ const skip = (d) => { skipped.push(d.slot); skippedDetail.push(d); };
19
+ if (!castLoras || typeof castLoras !== "object")
20
+ return { pretrained, voices, castIds, skipped, skippedDetail };
21
+ for (const [slot, raw] of Object.entries(castLoras)) {
22
+ if (typeof slot !== "string" || !slot.trim())
23
+ continue;
24
+ // S9 (F13): castLoras values are OPAQUE cast public ids from the browser, resolved to the internal
25
+ // int at THIS boundary. A bare integer (an enumeration probe) fails isPublicId and lands in the
26
+ // same "not a valid cast id" skip as any garbage -- with NO row data attached -- so the
27
+ // untrained-cast message can never become an id-enumeration oracle (harvesting names by counting).
28
+ if (!isPublicId(raw)) {
29
+ skip({ slot, reason: "not a valid cast id" });
30
+ continue;
31
+ }
32
+ const id = await getCastIdByPublicId(env, raw);
33
+ if (id === null) {
34
+ skip({ slot, reason: "cast member not found" });
35
+ continue;
36
+ }
37
+ castIds[slot] = id;
38
+ let cast = await getCastById(env, id);
39
+ if (cast?.lora_status === "training") {
40
+ cast = await refreshTrainingLora(env, cast);
41
+ }
42
+ // Voice rides the row we already fetched, independent of LoRA readiness.
43
+ if (cast)
44
+ voices[slot] = coerceVoiceId(cast.voice_id) ?? DEFAULT_VOICE_ID;
45
+ if (!cast) {
46
+ skip({ slot, reason: "cast member not found" });
47
+ continue;
48
+ }
49
+ if (cast.lora_status !== "ready" || !cast.lora_key || !cast.lora_key.startsWith("loras/")) {
50
+ skip({
51
+ slot, name: cast.name,
52
+ reason: cast.lora_status === "training" ? "LoRA still training" : "no trained LoRA",
53
+ });
54
+ continue;
55
+ }
56
+ pretrained[slot] = cast.lora_key;
57
+ }
58
+ return { pretrained, voices, castIds, skipped, skippedDetail };
59
+ }
60
+ /** Build an actionable, per-character rejection message from the skipped slots: name who needs
61
+ * training (falling back to the slot id when the cast row did not resolve) and where to do it. */
62
+ export function untrainedCastMessage(skippedDetail) {
63
+ const names = skippedDetail.map((d) => {
64
+ const who = d.name ?? `slot ${d.slot}`;
65
+ return d.reason === "LoRA still training" ? `${who} (still training)` : who;
66
+ });
67
+ return `These characters have no trained LoRA -- train them on the Cast page first: ${names.join(", ")}.`;
68
+ }
69
+ //# sourceMappingURL=cast-loras.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cast-loras.js","sourceRoot":"","sources":["../src/cast-loras.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import type { Env } from "./platform/orchestrator-context.js";
2
+ import type { ClipJob } from "./clip-job-model.js";
3
+ /** The video-finish container's POST /inspect response (containers/video-finish/app.py + inspect_core.py). */
4
+ export interface InspectResult {
5
+ ok: boolean;
6
+ verdict: "ok" | "suspect" | "corrupt";
7
+ reason?: string;
8
+ metrics?: {
9
+ sat_mean: number;
10
+ gray_std_mean: number;
11
+ chroma_structure_ratio: number;
12
+ frames: number;
13
+ };
14
+ keyframe_similarity?: number | null;
15
+ error?: string;
16
+ }
17
+ /** Call the video-finish container's POST /inspect, retrying the transient gateway statuses (503/504) the
18
+ * way callVideoFinish does for /finish. backoffMs is injectable so tests do not wait. Returns the parsed
19
+ * result, or null on an unreachable container / non-JSON / non-2xx (the caller treats null as skip). */
20
+ export declare function callVideoFinishInspect(env: Env, payload: {
21
+ clipUrl: string;
22
+ keyframeUrl?: string;
23
+ }, opts?: {
24
+ retries?: number;
25
+ backoffMs?: number;
26
+ }): Promise<InspectResult | null>;
27
+ export interface ContentVerdict {
28
+ verdict: "ok" | "suspect" | "corrupt" | "skip";
29
+ reason?: string;
30
+ metrics?: InspectResult["metrics"];
31
+ keyframe_similarity?: number | null;
32
+ }
33
+ /** Presign the clip (and its keyframe, when known) and ask the container for a content verdict. Skips
34
+ * honestly (never throws, never fails a render) when the tier is unavailable. */
35
+ export declare function contentValidateClip(env: Env, clipKey: string, keyframeKey?: string): Promise<ContentVerdict>;
36
+ /** #523 Layer 2 pass over a clip job's done clips, at the film finish gate. For each structurally-valid
37
+ * (Layer 1) done clip not yet content-checked, ask the container: a "corrupt" verdict FAILS the shot with
38
+ * the real reason BEFORE finish/upscale spend (honest failure); a "suspect" verdict records a warn/degrade
39
+ * marker and lets the film complete; "ok"/"skip" pass. Idempotent per shot (content_validated). Emits one
40
+ * `clip.content_validate` structured event per shot. Returns true iff it changed any shot; the CALLER owns
41
+ * the job-doc write. A no-op (returns false immediately) when the video-finish tier is not installed. */
42
+ export declare function contentValidateDoneClips(env: Env, job: ClipJob, inspect?: (env: Env, clipKey: string, keyframeKey?: string) => Promise<ContentVerdict>): Promise<boolean>;
43
+ //# sourceMappingURL=clip-content-validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clip-content-validate.d.ts","sourceRoot":"","sources":["../src/clip-content-validate.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,oCAAoC,CAAC;AAE9D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAMnD,8GAA8G;AAC9G,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,IAAI,GAAG,SAAS,GAAG,SAAS,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,sBAAsB,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACtG,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;yGAEyG;AACzG,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,GAAG,EACR,OAAO,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,EAClD,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAClD,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAuB/B;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,IAAI,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IACnC,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED;kFACkF;AAClF,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAalH;AAED;;;;;0GAK0G;AAC1G,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,OAAO,EACZ,OAAO,GAAE,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAuB,GAC1G,OAAO,CAAC,OAAO,CAAC,CA2BlB"}
@@ -0,0 +1,115 @@
1
+ // #523 Layer 2: pixel-content clip validation at the film finish gate.
2
+ //
3
+ // Layer 1 (src/clip-validate.ts) rejects STRUCTURAL corruption in-Worker but cannot decode pixels, so a
4
+ // structurally-valid clip of pure latent noise (local-16gb#35: CogVideoX on a vGPU) passes it. Layer 2
5
+ // closes that: it asks the video-finish CPU container (which already runs ffmpeg, and is already the
6
+ // finish/assemble dependency) to look at the actual frames, at the SAME clip-intake seam, BEFORE the
7
+ // finish / upscale GPU spend. Bytes never touch the Worker: the core presigns GET URLs and the container
8
+ // fetches them (the /finish presign pattern).
9
+ //
10
+ // Posture (warn-and-degrade default, #523's false-positive concern -- deliberately-abstract films exist):
11
+ // - "corrupt": the container is CONFIDENT (the clip's first frame does not resemble its conditioning
12
+ // keyframe -- the #35 signature). FAIL the shot with the real reason, before finish/upscale spend.
13
+ // - "suspect": the weaker content-only heuristic (chromatic-noise signature) fired. WARN: record a
14
+ // degrade marker; the film still completes. Never a hard fail on the heuristic alone.
15
+ // - "ok" / "skip": pass. "skip" = the tier is not installed (self-host), the container was unreachable,
16
+ // or the inspect errored -- a down inspector must never fail a real render.
17
+ //
18
+ // Runs at the film finish gate ONLY (where finish/upscale spend happens), not on the standalone clips
19
+ // route (which has no downstream spend); Layer 1 covers that route.
20
+ import { asFetcher } from "./platform/fetcher.js";
21
+ import { presignR2Get } from "./presign.js";
22
+ import { emitStructuredEvent } from "./structured-events.js";
23
+ const INSPECT_TTL_SECONDS = 1800;
24
+ /** Call the video-finish container's POST /inspect, retrying the transient gateway statuses (503/504) the
25
+ * way callVideoFinish does for /finish. backoffMs is injectable so tests do not wait. Returns the parsed
26
+ * result, or null on an unreachable container / non-JSON / non-2xx (the caller treats null as skip). */
27
+ export async function callVideoFinishInspect(env, payload, opts = {}) {
28
+ if (!env.VIDEO_FINISH_VPC)
29
+ return null; // tier not installed (stock self-host): skip, never fail
30
+ const vpc = asFetcher(env.VIDEO_FINISH_VPC);
31
+ if (!vpc)
32
+ return null;
33
+ const retries = opts.retries ?? 3;
34
+ const backoffMs = opts.backoffMs ?? 1500;
35
+ const init = { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(payload) };
36
+ let resp = null;
37
+ for (let attempt = 0; attempt < retries; attempt++) {
38
+ try {
39
+ resp = await vpc.fetch("http://video-finish/inspect", init);
40
+ }
41
+ catch {
42
+ resp = null;
43
+ }
44
+ if (resp && resp.status !== 503 && resp.status !== 504)
45
+ break;
46
+ if (attempt < retries - 1)
47
+ await new Promise((r) => setTimeout(r, backoffMs));
48
+ }
49
+ if (!resp || !resp.ok)
50
+ return null;
51
+ try {
52
+ return (await resp.json());
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ /** Presign the clip (and its keyframe, when known) and ask the container for a content verdict. Skips
59
+ * honestly (never throws, never fails a render) when the tier is unavailable. */
60
+ export async function contentValidateClip(env, clipKey, keyframeKey) {
61
+ if (!env.VIDEO_FINISH_VPC)
62
+ return { verdict: "skip", reason: "video-finish tier not installed (VIDEO_FINISH_VPC unbound)" };
63
+ let clipUrl;
64
+ let keyframeUrl;
65
+ try {
66
+ clipUrl = await presignR2Get(env, clipKey, INSPECT_TTL_SECONDS);
67
+ if (keyframeKey)
68
+ keyframeUrl = await presignR2Get(env, keyframeKey, INSPECT_TTL_SECONDS);
69
+ }
70
+ catch (e) {
71
+ return { verdict: "skip", reason: `presign failed: ${e instanceof Error ? e.message : String(e)}` };
72
+ }
73
+ const r = await callVideoFinishInspect(env, { clipUrl, keyframeUrl });
74
+ if (!r || !r.ok || !r.verdict)
75
+ return { verdict: "skip", reason: "video-finish /inspect unreachable or errored" };
76
+ return { verdict: r.verdict, reason: r.reason, metrics: r.metrics, keyframe_similarity: r.keyframe_similarity };
77
+ }
78
+ /** #523 Layer 2 pass over a clip job's done clips, at the film finish gate. For each structurally-valid
79
+ * (Layer 1) done clip not yet content-checked, ask the container: a "corrupt" verdict FAILS the shot with
80
+ * the real reason BEFORE finish/upscale spend (honest failure); a "suspect" verdict records a warn/degrade
81
+ * marker and lets the film complete; "ok"/"skip" pass. Idempotent per shot (content_validated). Emits one
82
+ * `clip.content_validate` structured event per shot. Returns true iff it changed any shot; the CALLER owns
83
+ * the job-doc write. A no-op (returns false immediately) when the video-finish tier is not installed. */
84
+ export async function contentValidateDoneClips(env, job, inspect = contentValidateClip) {
85
+ if (!env.VIDEO_FINISH_VPC)
86
+ return false; // tier not installed: Layer 2 is unavailable, Layer 1 stands
87
+ let changed = false;
88
+ for (const shot of job.shots) {
89
+ if (shot.status !== "done" || !shot.clip_key || shot.content_validated)
90
+ continue;
91
+ const v = await inspect(env, shot.clip_key, shot.keyframe_key);
92
+ shot.content_validated = v.verdict;
93
+ emitStructuredEvent({
94
+ ev: "clip.content_validate",
95
+ job_id: job.job_id,
96
+ shot_id: shot.shot_id,
97
+ verdict: v.verdict,
98
+ ...(v.keyframe_similarity != null ? { keyframe_similarity: v.keyframe_similarity } : {}),
99
+ ...(v.metrics ? { metrics: v.metrics } : {}),
100
+ ...(v.reason ? { reason: v.reason } : {}),
101
+ });
102
+ if (v.verdict === "corrupt") {
103
+ shot.status = "failed";
104
+ shot.error = `clip failed content validation: ${v.reason ?? "does not resemble its keyframe"}`;
105
+ shot.poll = undefined;
106
+ changed = true;
107
+ }
108
+ else if (v.verdict === "suspect") {
109
+ shot.content_degraded = v.reason ?? "chromatic-noise signature"; // warn-and-degrade: film still completes
110
+ changed = true;
111
+ }
112
+ }
113
+ return changed;
114
+ }
115
+ //# sourceMappingURL=clip-content-validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clip-content-validate.js","sourceRoot":"","sources":["../src/clip-content-validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ export interface ClipShotInput {
2
+ shot_id: string;
3
+ keyframe_url: string;
4
+ keyframe_key?: string;
5
+ prompt: string;
6
+ seconds: number;
7
+ motion_backend?: string;
8
+ }
9
+ export interface ClipShot extends ClipShotInput {
10
+ status: "pending" | "done" | "failed";
11
+ poll?: string;
12
+ clip_key?: string;
13
+ error?: string;
14
+ binding?: string | null;
15
+ runpod_job_id?: string;
16
+ cancel_sent?: boolean;
17
+ validated?: "pass" | "fail" | "skip";
18
+ content_validated?: "ok" | "suspect" | "corrupt" | "skip";
19
+ content_degraded?: string;
20
+ delivered_fps?: number;
21
+ delivered_frames?: number;
22
+ distilled?: boolean;
23
+ poll_attempts?: number;
24
+ }
25
+ export interface ClipJob {
26
+ job_id: string;
27
+ project: string;
28
+ motion_backend: string | null;
29
+ binding: string | null;
30
+ module_configs?: Record<string, Record<string, unknown>>;
31
+ shots: ClipShot[];
32
+ created_at: number;
33
+ }
34
+ export interface JobSummary {
35
+ total: number;
36
+ done: number;
37
+ failed: number;
38
+ pending: number;
39
+ complete: boolean;
40
+ }
41
+ export declare function summarizeJob(job: ClipJob): JobSummary;
42
+ //# sourceMappingURL=clip-job-model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clip-job-model.d.ts","sourceRoot":"","sources":["../src/clip-job-model.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,QAAS,SAAQ,aAAa;IAC7C,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACrC,iBAAiB,CAAC,EAAE,IAAI,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;IAC1D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IAGpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACzD,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,CAKrD"}
@@ -0,0 +1,8 @@
1
+ // Pure clip-job shapes + summary (shared by film-model and render-orchestrator).
2
+ export function summarizeJob(job) {
3
+ const total = job.shots.length;
4
+ const done = job.shots.filter((s) => s.status === "done").length;
5
+ const failed = job.shots.filter((s) => s.status === "failed").length;
6
+ return { total, done, failed, pending: total - done - failed, complete: done + failed === total };
7
+ }
8
+ //# sourceMappingURL=clip-job-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clip-job-model.js","sourceRoot":"","sources":["../src/clip-job-model.ts"],"names":[],"mappings":"AAAA,iFAAiF;AAgDjF,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC;IAC/B,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IACjE,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,MAAM,CAAC;IACrE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,GAAG,IAAI,GAAG,MAAM,EAAE,QAAQ,EAAE,IAAI,GAAG,MAAM,KAAK,KAAK,EAAE,CAAC;AACpG,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { Env } from "./platform/orchestrator-context.js";
2
+ export declare const CLIP_VALIDATE_ENABLED = true;
3
+ export declare const CLIP_MIN_BYTES = 2048;
4
+ export declare const CLIP_MIN_DURATION_S = 0.15;
5
+ export declare const CLIP_MAX_DURATION_S = 900;
6
+ export declare const CLIP_MAX_DIMENSION = 16384;
7
+ export declare const MOOV_FETCH_CAP: number;
8
+ export interface ClipValidateChecks {
9
+ container: boolean;
10
+ video_track: boolean;
11
+ duration_s: number | null;
12
+ expected_s: number;
13
+ frames: number | null;
14
+ width: number | null;
15
+ height: number | null;
16
+ bytes: number;
17
+ }
18
+ export interface ClipValidateResult {
19
+ verdict: "pass" | "fail" | "skip";
20
+ reason?: string;
21
+ checks: ClipValidateChecks;
22
+ }
23
+ export interface MoovInfo {
24
+ durationS: number | null;
25
+ hasVideoTrack: boolean;
26
+ frames: number | null;
27
+ width: number | null;
28
+ height: number | null;
29
+ }
30
+ export declare function parseMoov(buf: Uint8Array, start?: number, end?: number): MoovInfo;
31
+ export declare function judgeClip(checks: ClipValidateChecks): ClipValidateResult;
32
+ export declare function validateClipArtifact(env: Env, key: string, expectedSeconds: number): Promise<ClipValidateResult>;
33
+ //# sourceMappingURL=clip-validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clip-validate.d.ts","sourceRoot":"","sources":["../src/clip-validate.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,oCAAoC,CAAC;AAM9D,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAC1C,eAAO,MAAM,cAAc,OAAO,CAAC;AACnC,eAAO,MAAM,mBAAmB,OAAO,CAAC;AACxC,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAIxC,eAAO,MAAM,cAAc,QAAkB,CAAC;AAI9C,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf;AACD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAmCD,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAMD,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,SAAI,EAAE,GAAG,GAAE,MAAmB,GAAG,QAAQ,CAIxF;AAyED,wBAAgB,SAAS,CAAC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,CAqBxE;AA4BD,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAsCtH"}