gooseworks 0.5.0-dev.9.d3e7757f → 0.5.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 (220) hide show
  1. package/README.md +17 -26
  2. package/dist/auth/device-flow.d.ts +10 -0
  3. package/dist/auth/device-flow.d.ts.map +1 -1
  4. package/dist/auth/device-flow.js +39 -1
  5. package/dist/auth/device-flow.js.map +1 -1
  6. package/dist/commands/doctor.d.ts.map +1 -1
  7. package/dist/commands/doctor.js +3 -1
  8. package/dist/commands/doctor.js.map +1 -1
  9. package/dist/commands/video.d.ts +5 -0
  10. package/dist/commands/video.d.ts.map +1 -0
  11. package/dist/commands/video.js +117 -0
  12. package/dist/commands/video.js.map +1 -0
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js +5 -2
  15. package/dist/config.js.map +1 -1
  16. package/dist/environment.d.ts.map +1 -1
  17. package/dist/environment.js +4 -2
  18. package/dist/environment.js.map +1 -1
  19. package/dist/kit/core/bind.d.ts +12 -0
  20. package/dist/kit/core/bind.d.ts.map +1 -0
  21. package/dist/kit/core/bind.js +77 -0
  22. package/dist/kit/core/bind.js.map +1 -0
  23. package/dist/kit/core/canonical.d.ts +30 -0
  24. package/dist/kit/core/canonical.d.ts.map +1 -0
  25. package/dist/kit/core/canonical.js +67 -0
  26. package/dist/kit/core/canonical.js.map +1 -0
  27. package/dist/kit/core/check.d.ts +22 -0
  28. package/dist/kit/core/check.d.ts.map +1 -0
  29. package/dist/kit/core/check.js +53 -0
  30. package/dist/kit/core/check.js.map +1 -0
  31. package/dist/kit/core/device.d.ts +17 -0
  32. package/dist/kit/core/device.d.ts.map +1 -0
  33. package/dist/kit/core/device.js +153 -0
  34. package/dist/kit/core/device.js.map +1 -0
  35. package/dist/kit/core/errors.d.ts +22 -0
  36. package/dist/kit/core/errors.d.ts.map +1 -0
  37. package/dist/kit/core/errors.js +49 -0
  38. package/dist/kit/core/errors.js.map +1 -0
  39. package/dist/kit/core/files.d.ts +49 -0
  40. package/dist/kit/core/files.d.ts.map +1 -0
  41. package/dist/kit/core/files.js +213 -0
  42. package/dist/kit/core/files.js.map +1 -0
  43. package/dist/kit/core/host.d.ts +26 -0
  44. package/dist/kit/core/host.d.ts.map +1 -0
  45. package/dist/kit/core/host.js +10 -0
  46. package/dist/kit/core/host.js.map +1 -0
  47. package/dist/kit/core/log.d.ts +17 -0
  48. package/dist/kit/core/log.d.ts.map +1 -0
  49. package/dist/kit/core/log.js +82 -0
  50. package/dist/kit/core/log.js.map +1 -0
  51. package/dist/kit/core/paths.d.ts +27 -0
  52. package/dist/kit/core/paths.d.ts.map +1 -0
  53. package/dist/kit/core/paths.js +103 -0
  54. package/dist/kit/core/paths.js.map +1 -0
  55. package/dist/kit/core/pieces.d.ts +84 -0
  56. package/dist/kit/core/pieces.d.ts.map +1 -0
  57. package/dist/kit/core/pieces.js +374 -0
  58. package/dist/kit/core/pieces.js.map +1 -0
  59. package/dist/kit/core/progress-reporter.d.ts +30 -0
  60. package/dist/kit/core/progress-reporter.d.ts.map +1 -0
  61. package/dist/kit/core/progress-reporter.js +106 -0
  62. package/dist/kit/core/progress-reporter.js.map +1 -0
  63. package/dist/kit/core/progress.d.ts +22 -0
  64. package/dist/kit/core/progress.d.ts.map +1 -0
  65. package/dist/kit/core/progress.js +90 -0
  66. package/dist/kit/core/progress.js.map +1 -0
  67. package/dist/kit/core/run.d.ts +35 -0
  68. package/dist/kit/core/run.d.ts.map +1 -0
  69. package/dist/kit/core/run.js +850 -0
  70. package/dist/kit/core/run.js.map +1 -0
  71. package/dist/kit/core/save.d.ts +43 -0
  72. package/dist/kit/core/save.d.ts.map +1 -0
  73. package/dist/kit/core/save.js +265 -0
  74. package/dist/kit/core/save.js.map +1 -0
  75. package/dist/kit/core/schema.d.ts +5 -0
  76. package/dist/kit/core/schema.d.ts.map +1 -0
  77. package/dist/kit/core/schema.js +183 -0
  78. package/dist/kit/core/schema.js.map +1 -0
  79. package/dist/kit/core/secrets.d.ts +9 -0
  80. package/dist/kit/core/secrets.d.ts.map +1 -0
  81. package/dist/kit/core/secrets.js +60 -0
  82. package/dist/kit/core/secrets.js.map +1 -0
  83. package/dist/kit/core/style.d.ts +59 -0
  84. package/dist/kit/core/style.d.ts.map +1 -0
  85. package/dist/kit/core/style.js +179 -0
  86. package/dist/kit/core/style.js.map +1 -0
  87. package/dist/kit/core/toolchain.d.ts +52 -0
  88. package/dist/kit/core/toolchain.d.ts.map +1 -0
  89. package/dist/kit/core/toolchain.js +321 -0
  90. package/dist/kit/core/toolchain.js.map +1 -0
  91. package/dist/kit/core/version.d.ts +18 -0
  92. package/dist/kit/core/version.d.ts.map +1 -0
  93. package/dist/kit/core/version.js +33 -0
  94. package/dist/kit/core/version.js.map +1 -0
  95. package/dist/kit/env.d.ts +42 -0
  96. package/dist/kit/env.d.ts.map +1 -0
  97. package/dist/kit/env.js +76 -0
  98. package/dist/kit/env.js.map +1 -0
  99. package/dist/kit/line/client.d.ts +82 -0
  100. package/dist/kit/line/client.d.ts.map +1 -0
  101. package/dist/kit/line/client.js +325 -0
  102. package/dist/kit/line/client.js.map +1 -0
  103. package/dist/kit/line/types.d.ts +178 -0
  104. package/dist/kit/line/types.d.ts.map +1 -0
  105. package/dist/kit/line/types.js +18 -0
  106. package/dist/kit/line/types.js.map +1 -0
  107. package/dist/kit/maker/browser.d.ts +114 -0
  108. package/dist/kit/maker/browser.d.ts.map +1 -0
  109. package/dist/kit/maker/browser.js +547 -0
  110. package/dist/kit/maker/browser.js.map +1 -0
  111. package/dist/kit/maker/encode.d.ts +27 -0
  112. package/dist/kit/maker/encode.d.ts.map +1 -0
  113. package/dist/kit/maker/encode.js +58 -0
  114. package/dist/kit/maker/encode.js.map +1 -0
  115. package/dist/kit/maker/images.d.ts +14 -0
  116. package/dist/kit/maker/images.d.ts.map +1 -0
  117. package/dist/kit/maker/images.js +174 -0
  118. package/dist/kit/maker/images.js.map +1 -0
  119. package/dist/kit/maker/inputs.d.ts +81 -0
  120. package/dist/kit/maker/inputs.d.ts.map +1 -0
  121. package/dist/kit/maker/inputs.js +235 -0
  122. package/dist/kit/maker/inputs.js.map +1 -0
  123. package/dist/kit/maker/make.d.ts +19 -0
  124. package/dist/kit/maker/make.d.ts.map +1 -0
  125. package/dist/kit/maker/make.js +272 -0
  126. package/dist/kit/maker/make.js.map +1 -0
  127. package/dist/kit/maker/page.d.ts +19 -0
  128. package/dist/kit/maker/page.d.ts.map +1 -0
  129. package/dist/kit/maker/page.js +276 -0
  130. package/dist/kit/maker/page.js.map +1 -0
  131. package/dist/kit/maker/parts/html-frames/1.0.0/part.json +183 -0
  132. package/dist/kit/maker/parts/html-frames/src/part.d.ts +3 -0
  133. package/dist/kit/maker/parts/html-frames/src/part.d.ts.map +1 -0
  134. package/dist/kit/maker/parts/html-frames/src/part.js +10 -0
  135. package/dist/kit/maker/parts/html-frames/src/part.js.map +1 -0
  136. package/dist/kit/maker/runtime.d.ts +7 -0
  137. package/dist/kit/maker/runtime.d.ts.map +1 -0
  138. package/dist/kit/maker/runtime.js +571 -0
  139. package/dist/kit/maker/runtime.js.map +1 -0
  140. package/dist/kit/maker/unzip.d.ts +3 -0
  141. package/dist/kit/maker/unzip.d.ts.map +1 -0
  142. package/dist/kit/maker/unzip.js +133 -0
  143. package/dist/kit/maker/unzip.js.map +1 -0
  144. package/dist/kit/parts/catalog.d.ts +47 -0
  145. package/dist/kit/parts/catalog.d.ts.map +1 -0
  146. package/dist/kit/parts/catalog.js +198 -0
  147. package/dist/kit/parts/catalog.js.map +1 -0
  148. package/dist/kit/parts/fs-safe.d.ts +32 -0
  149. package/dist/kit/parts/fs-safe.d.ts.map +1 -0
  150. package/dist/kit/parts/fs-safe.js +185 -0
  151. package/dist/kit/parts/fs-safe.js.map +1 -0
  152. package/dist/kit/parts/loader.d.ts +50 -0
  153. package/dist/kit/parts/loader.d.ts.map +1 -0
  154. package/dist/kit/parts/loader.js +395 -0
  155. package/dist/kit/parts/loader.js.map +1 -0
  156. package/dist/kit/parts/semver.d.ts +6 -0
  157. package/dist/kit/parts/semver.d.ts.map +1 -0
  158. package/dist/kit/parts/semver.js +44 -0
  159. package/dist/kit/parts/semver.js.map +1 -0
  160. package/dist/program.d.ts.map +1 -1
  161. package/dist/program.js +6 -6
  162. package/dist/program.js.map +1 -1
  163. package/dist/skills/installer.d.ts +22 -0
  164. package/dist/skills/installer.d.ts.map +1 -1
  165. package/dist/skills/installer.js +56 -8
  166. package/dist/skills/installer.js.map +1 -1
  167. package/dist/skills/master-skill.d.ts +11 -70
  168. package/dist/skills/master-skill.d.ts.map +1 -1
  169. package/dist/skills/master-skill.js +120 -2900
  170. package/dist/skills/master-skill.js.map +1 -1
  171. package/dist/skills/package-freshness.d.ts +19 -0
  172. package/dist/skills/package-freshness.d.ts.map +1 -1
  173. package/dist/skills/package-freshness.js +37 -2
  174. package/dist/skills/package-freshness.js.map +1 -1
  175. package/dist/skills/routes.d.ts +7 -2
  176. package/dist/skills/routes.d.ts.map +1 -1
  177. package/dist/skills/routes.js +21 -25
  178. package/dist/skills/routes.js.map +1 -1
  179. package/dist/skills/server-entry-skills.d.ts +14 -0
  180. package/dist/skills/server-entry-skills.d.ts.map +1 -0
  181. package/dist/skills/server-entry-skills.js +119 -0
  182. package/dist/skills/server-entry-skills.js.map +1 -0
  183. package/dist/skills/staging-content.js +1 -1
  184. package/package.json +10 -6
  185. package/skills/goose-ads/SKILL.md +27 -605
  186. package/skills/goose-product-photos/SKILL.md +24 -217
  187. package/skills/goose-video/SKILL.md +23 -610
  188. package/skills/goose-video-local/SKILL.md +3 -1609
  189. package/skills/gooseworks/SKILL.md +58 -529
  190. package/skills/make-custom-video/SKILL.md +23 -725
  191. package/skills/manifest.json +7 -7
  192. package/skills/routes.json +10 -24
  193. package/dist/commands/video-local.d.ts +0 -9
  194. package/dist/commands/video-local.d.ts.map +0 -1
  195. package/dist/commands/video-local.js +0 -83
  196. package/dist/commands/video-local.js.map +0 -1
  197. package/dist/commands/video-save.d.ts +0 -3
  198. package/dist/commands/video-save.d.ts.map +0 -1
  199. package/dist/commands/video-save.js +0 -83
  200. package/dist/commands/video-save.js.map +0 -1
  201. package/dist/lib/video-local-captions.d.ts +0 -83
  202. package/dist/lib/video-local-captions.d.ts.map +0 -1
  203. package/dist/lib/video-local-captions.js +0 -261
  204. package/dist/lib/video-local-captions.js.map +0 -1
  205. package/dist/lib/video-local-runtime.d.ts +0 -8
  206. package/dist/lib/video-local-runtime.d.ts.map +0 -1
  207. package/dist/lib/video-local-runtime.js +0 -201
  208. package/dist/lib/video-local-runtime.js.map +0 -1
  209. package/dist/lib/video-save-mcp.d.ts +0 -9
  210. package/dist/lib/video-save-mcp.d.ts.map +0 -1
  211. package/dist/lib/video-save-mcp.js +0 -162
  212. package/dist/lib/video-save-mcp.js.map +0 -1
  213. package/dist/lib/video-save.d.ts +0 -77
  214. package/dist/lib/video-save.d.ts.map +0 -1
  215. package/dist/lib/video-save.js +0 -440
  216. package/dist/lib/video-save.js.map +0 -1
  217. package/dist/skills/custom-video-skill.d.ts +0 -3
  218. package/dist/skills/custom-video-skill.d.ts.map +0 -1
  219. package/dist/skills/custom-video-skill.js +0 -6
  220. package/dist/skills/custom-video-skill.js.map +0 -1
@@ -0,0 +1,850 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.LAYER_ORDER = void 0;
37
+ exports.makeVideo = makeVideo;
38
+ // `gooseworks video make <id>`, in order (part-interface.md section 8):
39
+ // 1. the hand-over (token, frozen plan, parts lock, style package);
40
+ // 2. a kit that is too old stops here and updates itself;
41
+ // 3. the run folder and its lock; the style package, checked;
42
+ // 4. every locked part loaded and checked: nothing is spent before this;
43
+ // 5. the plan's files downloaded and checked;
44
+ // 6. the style's timeline in order, saving after every piece;
45
+ // 7. the layers that are on, in the fixed order brand, captions, sound, check;
46
+ // 8. one local fix when the check fails;
47
+ // 9. the upload and our server's check, with one fix after a first fail;
48
+ // 10. done. The run folder stays, so the same command resumes at any time.
49
+ //
50
+ // The core knows the style file's grammar, the part interface, the four layer
51
+ // slots, the line and the save layout. It knows nothing about any one style.
52
+ const promises_1 = require("fs/promises");
53
+ const path = __importStar(require("path"));
54
+ const client_1 = require("../line/client");
55
+ const types_1 = require("../line/types");
56
+ const bind_1 = require("./bind");
57
+ const canonical_1 = require("./canonical");
58
+ const device_1 = require("./device");
59
+ const errors_1 = require("./errors");
60
+ const files_1 = require("./files");
61
+ const paths_1 = require("./paths");
62
+ const pieces_1 = require("./pieces");
63
+ const progress_1 = require("./progress");
64
+ const progress_reporter_1 = require("./progress-reporter");
65
+ const save_1 = require("./save");
66
+ const schema_1 = require("./schema");
67
+ const secrets_1 = require("./secrets");
68
+ const style_1 = require("./style");
69
+ const toolchain_1 = require("./toolchain");
70
+ const loader_1 = require("../parts/loader");
71
+ const env_1 = require("../env");
72
+ const version_1 = require("./version");
73
+ exports.LAYER_ORDER = ['brand', 'captions', 'sound', 'check'];
74
+ const SLOT_KIND = { brand: 'compose', captions: 'caption', sound: 'mix', check: 'check' };
75
+ /** The server's checks a layer can redo. */
76
+ const SERVER_CHECK_SLOT = { sound: 'sound', captions: 'captions' };
77
+ /** Pixel size of each aspect the plan may pick (short side 1080). */
78
+ const ASPECT_SIZE = {
79
+ '9:16': { width: 1080, height: 1920 },
80
+ '1:1': { width: 1080, height: 1080 },
81
+ '4:5': { width: 1080, height: 1350 },
82
+ '16:9': { width: 1920, height: 1080 },
83
+ };
84
+ function lineWords(error) {
85
+ return [error.message, error.fix].filter(Boolean).join(' ');
86
+ }
87
+ /** Plain words for a part's failure: the line's own words when it said them, never a part's detail. */
88
+ function failureWords(error) {
89
+ if (error instanceof pieces_1.PieceFailure)
90
+ return [error.line.error, error.line.fix].filter(Boolean).join(' ');
91
+ switch (error.code) {
92
+ case 'provider_rejected':
93
+ return 'One part of this video was refused by the maker. Change that scene in the plan, then make it again.';
94
+ case 'over_quote':
95
+ return 'This video reached its approved price, so it stopped. Nothing past the price was charged.';
96
+ case 'needs_missing':
97
+ return 'This computer is missing something this video needs. Run the computer check, then make it again.';
98
+ case 'timeout':
99
+ return 'Part of this video took too long. Run the same command again; what was made so far is kept.';
100
+ default:
101
+ return 'Part of this video could not be made. Run the same command again; what was made so far is kept.';
102
+ }
103
+ }
104
+ function checkLock(raw, videoId, plan) {
105
+ const lock = raw;
106
+ if (!lock || lock.lock !== 1 || lock.video_id !== videoId || !lock.parts || !lock.layers || !lock.style) {
107
+ throw new errors_1.KitStop('This video’s parts list can’t be read. Nothing was spent.', 'change_request');
108
+ }
109
+ if (lock.style.id !== plan.style_id || lock.style.version !== plan.style_version)
110
+ throw new errors_1.KitStop('This video’s parts list is for another style. Nothing was spent.', 'change_request');
111
+ return lock;
112
+ }
113
+ function expectationOf(style, plan) {
114
+ const aspect = String(plan.body.aspect ?? style.aspects?.[0] ?? '');
115
+ const size = ASPECT_SIZE[aspect];
116
+ if (!size)
117
+ throw new errors_1.KitStop('This video’s shape can’t be made by the kit.', 'change_request');
118
+ const scenes = Array.isArray(plan.body.scenes) ? plan.body.scenes : [];
119
+ const script = scenes.map((s) => s?.line).filter((l) => typeof l === 'string' && l.length > 0);
120
+ return {
121
+ aspect: aspect,
122
+ ...size,
123
+ duration_s: { min: style.duration.min_seconds, max: style.duration.max_seconds },
124
+ speech: (style.traits?.speech ?? 'none'),
125
+ captions: style.traits?.captions === true,
126
+ end_card: style.traits?.end_card === true,
127
+ qc_flags: Array.isArray(style.traits?.qc_flags) ? style.traits.qc_flags : [],
128
+ ...(script.length ? { script } : {}),
129
+ };
130
+ }
131
+ function brandKit(materialized) {
132
+ const out = {};
133
+ for (const [k, v] of Object.entries((materialized ?? {})))
134
+ if (v !== null && v !== undefined)
135
+ out[k] = v;
136
+ return out;
137
+ }
138
+ function fileRefsIn(value, into = []) {
139
+ if ((0, canonical_1.isFileRef)(value))
140
+ into.push(value);
141
+ else if (Array.isArray(value))
142
+ value.forEach((v) => fileRefsIn(v, into));
143
+ else if (value && typeof value === 'object')
144
+ Object.values(value).forEach((v) => fileRefsIn(v, into));
145
+ return into;
146
+ }
147
+ /** One make of one video. */
148
+ class Maker {
149
+ videoId;
150
+ deps;
151
+ sleep;
152
+ now;
153
+ stop = new AbortController();
154
+ fatal = null;
155
+ book = new progress_1.ProgressBook();
156
+ reporter;
157
+ store;
158
+ layout;
159
+ run;
160
+ cache;
161
+ tools;
162
+ toolchain = '';
163
+ browserReady = false;
164
+ handed;
165
+ lock = null;
166
+ style;
167
+ planScope;
168
+ brand;
169
+ parts = new Map();
170
+ hosted = new Map();
171
+ pieceLocks = new Map();
172
+ constructor(videoId, deps) {
173
+ this.videoId = videoId;
174
+ this.deps = deps;
175
+ this.sleep = deps.sleep ?? client_1.sleep;
176
+ this.now = deps.now ?? (() => new Date());
177
+ deps.signal?.addEventListener('abort', () => this.halt(new errors_1.KitStop('Stopped on this computer. Run the same command to carry on.', 'stop')), { once: true });
178
+ }
179
+ halt(stop) {
180
+ this.fatal ??= stop;
181
+ if (!this.stop.signal.aborted)
182
+ this.stop.abort(stop);
183
+ }
184
+ say(line) {
185
+ this.deps.log.say(line);
186
+ }
187
+ async make() {
188
+ const { deps } = this;
189
+ let dev;
190
+ try {
191
+ dev = this.devFolders();
192
+ }
193
+ catch (error) {
194
+ return this.ended(error);
195
+ }
196
+ this.tools = deps.tools ?? (await (0, toolchain_1.inspectFfmpeg)({ home: deps.home, env: deps.env, setup: true, download: (u, m) => deps.line.download(u, m), say: (l) => this.say(l) }));
197
+ const browser = await deps.host.browser.check({ home: deps.home, setup: true, env: deps.env, say: (l) => this.say(l) });
198
+ this.browserReady = browser.ok;
199
+ this.toolchain = (0, toolchain_1.toolchainId)(this.tools, browser.ok ? browser.version : null);
200
+ const report = await (0, device_1.deviceReport)({ home: deps.home, worker: deps.line.isWorker, tools: this.tools, browser });
201
+ let answer;
202
+ try {
203
+ answer = await deps.line.handOver(this.videoId, report);
204
+ }
205
+ catch (error) {
206
+ if (error instanceof client_1.LineError) {
207
+ if (error.next === 'update_kit')
208
+ return { status: 'update_kit', message: lineWords(error) };
209
+ return { status: 'stopped', message: lineWords(error) };
210
+ }
211
+ throw error;
212
+ }
213
+ if (!answer.kit.ok)
214
+ return { status: 'update_kit', message: 'This video kit is too old for this video.', min_version: answer.kit.min_version };
215
+ if (!answer.line)
216
+ return { status: 'stopped', message: 'This video can’t be made on this computer right now.' };
217
+ this.handed = answer.line;
218
+ this.layout = (0, paths_1.runLayout)(deps.home, this.videoId);
219
+ const release = await (0, save_1.takeRunLock)(this.layout, this.now());
220
+ // From here on the lock is always given back, whatever fails.
221
+ try {
222
+ deps.log.attach(this.layout.log);
223
+ deps.log.write('info', 'video handed over', { quote_id: this.handed.quote_id, stage: this.handed.stage, kit: version_1.KIT_VERSION });
224
+ this.store = new save_1.RunStore(this.layout);
225
+ this.cache = new pieces_1.PieceCache(this.layout.pieces);
226
+ this.reporter = new progress_reporter_1.ProgressReporter(deps.line, this.videoId, this.book, deps.log, (message, reason, code) => this.halt(new errors_1.KitStop(message, reason, code)), deps.heartbeatMs, this.stop.signal);
227
+ // Reports start now, so checking the style and parts never looks quiet.
228
+ this.reporter.start();
229
+ void this.reporter.send(true);
230
+ return await this.makeHandedOver(dev);
231
+ }
232
+ catch (error) {
233
+ return await this.ended(error);
234
+ }
235
+ finally {
236
+ this.reporter?.stopTimer();
237
+ await this.reporter?.flush().catch(() => undefined);
238
+ await release();
239
+ }
240
+ }
241
+ devFolders() {
242
+ const parts = this.deps.env[env_1.KIT_DEV_ENV.partsDir] || null;
243
+ const styles = this.deps.env[env_1.KIT_DEV_ENV.stylesDir] || null;
244
+ if ((parts || styles) && this.deps.environment === 'production') {
245
+ throw new errors_1.KitStop('The dev parts and styles folders work only against staging or a local server.', 'refused');
246
+ }
247
+ return { parts, styles };
248
+ }
249
+ async ended(error) {
250
+ const stop = this.fatal ?? (error instanceof errors_1.KitStop ? error : null);
251
+ if (stop)
252
+ this.deps.log.write('warn', 'the run stopped', { reason: stop.reason, code: stop.code ?? null, message: stop.message });
253
+ if (this.run) {
254
+ this.run.status = stop && stop.reason === 'stop' ? 'stopped' : 'failed';
255
+ await this.saveRun().catch(() => undefined);
256
+ }
257
+ if (stop) {
258
+ if (stop.reason === 'update_kit')
259
+ return { status: 'update_kit', message: stop.message };
260
+ return { status: stop.reason === 'stop' ? 'stopped' : 'failed', message: stop.message };
261
+ }
262
+ this.deps.log.write('error', 'the run ended on an error', { error: error instanceof Error ? error.message : String(error) });
263
+ return { status: 'failed', message: 'This video could not be made right now. Run the same command again; what was made so far is kept.' };
264
+ }
265
+ async saveRun() {
266
+ this.run.updated_at = this.now().toISOString();
267
+ await this.store.writeRun(this.run);
268
+ }
269
+ async makeHandedOver(dev) {
270
+ const { deps, handed } = this;
271
+ const plan = handed.plan;
272
+ if (!plan || plan.project_id !== this.videoId || plan.style_id !== handed.style_package?.style_id || plan.style_version !== handed.style_package?.version) {
273
+ throw new errors_1.KitStop('The plan handed over is not this video’s. Nothing was spent.', 'change_request');
274
+ }
275
+ if (handed.parts_lock)
276
+ this.lock = checkLock(handed.parts_lock, this.videoId, plan);
277
+ else if (!dev.parts)
278
+ throw new errors_1.KitStop('This video’s parts are not ready yet. Run the same command again in a few minutes. Nothing was spent.', 'stop');
279
+ const planCopy = (0, secrets_1.withoutSignedLinks)(plan);
280
+ await (0, promises_1.mkdir)(this.layout.root, { recursive: true, mode: 0o700 });
281
+ await (0, save_1.writeJson)(this.layout.plan, planCopy);
282
+ if (this.lock)
283
+ await (0, save_1.writeJson)(this.layout.partsLock, this.lock);
284
+ const previous = (await this.store.readRun());
285
+ const started = this.now().toISOString();
286
+ this.run = {
287
+ record: 1,
288
+ video_id: this.videoId,
289
+ quote_id: handed.quote_id,
290
+ plan_revision: plan.revision,
291
+ style: { id: plan.style_id, version: plan.style_version },
292
+ plan_sha256: (0, canonical_1.canonicalHash)(planCopy),
293
+ lock_sha256: this.lock ? (0, canonical_1.canonicalHash)(this.lock) : '',
294
+ device_id: await (0, device_1.deviceId)(deps.home),
295
+ kit: { version: version_1.KIT_VERSION, toolchain: this.toolchain, parts_source: dev.parts ? 'dev' : 'published' },
296
+ status: 'running',
297
+ steps: previous?.steps ?? {},
298
+ local_fix_used: previous?.quote_id === handed.quote_id ? previous.local_fix_used : false,
299
+ fixes: previous?.quote_id === handed.quote_id ? (previous.fixes ?? []) : [],
300
+ upload_attempt: previous?.quote_id === handed.quote_id ? previous.upload_attempt : 0,
301
+ started_at: previous?.started_at ?? started,
302
+ updated_at: started,
303
+ };
304
+ await this.saveRun();
305
+ // The style, then every part: all checked before anything is spent.
306
+ // The style must be exactly the one approved: the package's hash and the plan's must agree.
307
+ if (!plan.style_hash || handed.style_package.style_hash !== plan.style_hash) {
308
+ throw new errors_1.KitStop('The style handed over is not the one approved for this video. Nothing was spent.', 'change_request');
309
+ }
310
+ const pin = { id: plan.style_id, version: plan.style_version, hash: plan.style_hash };
311
+ this.style = dev.styles
312
+ ? await (0, style_1.readLocalStyle)(dev.styles, pin)
313
+ : await (0, style_1.fetchStyle)({ ref: handed.style_package, projectId: this.videoId, dir: this.layout.style, download: (u, m) => deps.line.download(u, m, this.stop.signal) });
314
+ await this.loadParts(!!dev.parts);
315
+ // The plan's files, checked against the hashes frozen at the yes.
316
+ const planFiles = {
317
+ dir: this.layout.inputs,
318
+ download: (u, m) => deps.line.download(u, m, this.stop.signal),
319
+ hostedLink: (fileId) => deps.line.hostedFileLink(this.videoId, fileId, this.stop.signal).catch((error) => {
320
+ if (error instanceof client_1.LineError)
321
+ throw new errors_1.KitStop(`${lineWords(error)} Nothing was spent.`, error.next === 'update_kit' ? 'update_kit' : error.next === 'change_request' ? 'change_request' : 'stop', error.code);
322
+ throw error;
323
+ }),
324
+ // A hosted plan file goes into a payload as the line already knows it.
325
+ onHosted: (sha256, ref) => this.hosted.set(sha256, ref),
326
+ };
327
+ this.planScope = await (0, files_1.materialize)(plan.body, planFiles);
328
+ this.brand = brandKit(await (0, files_1.materialize)(plan.brand, planFiles));
329
+ this.book.note = 'Making your video';
330
+ this.book.plan([
331
+ ...this.style.style.timeline.map((s) => ({ id: s.id, kind: this.parts.get(s.id).manifest.kind, typical_s: this.parts.get(s.id).manifest.timing.typical_s })),
332
+ ...this.layersOn().map((slot) => ({ id: `layer-${slot}`, kind: SLOT_KIND[slot], typical_s: this.parts.get(`layer-${slot}`).manifest.timing.typical_s })),
333
+ ]);
334
+ void this.reporter.send(true);
335
+ // The fixes already in force (after a restart) apply from the start.
336
+ let result = await this.pipeline();
337
+ if (!result.verdict.pass) {
338
+ const fix = this.run.local_fix_used ? null : this.fixFrom(result.verdict, [], 'local');
339
+ if (!fix)
340
+ return this.checkFailed(result.verdict);
341
+ this.run.local_fix_used = true;
342
+ this.run.fixes = [...(this.run.fixes ?? []), fix];
343
+ await this.saveRun();
344
+ this.say('The final check found something to fix. Fixing it once…');
345
+ result = await this.pipeline();
346
+ if (!result.verdict.pass)
347
+ return this.checkFailed(result.verdict);
348
+ }
349
+ return this.upload(result);
350
+ }
351
+ layersOn() {
352
+ const layers = this.style.style.layers;
353
+ return exports.LAYER_ORDER.filter((slot) => slot === 'check' || layers[slot] === true);
354
+ }
355
+ /** Every part this video runs, from the lock, checked before anything is spent. */
356
+ async loadParts(dev) {
357
+ const lock = this.lock;
358
+ const layerRefs = lock?.layers ?? this.handed.plan.layers;
359
+ const wanted = [
360
+ ...this.style.style.timeline.map((s) => ({ key: s.id, ref: s.part })),
361
+ ...this.layersOn().map((slot) => ({ key: `layer-${slot}`, ref: layerRefs?.[slot], slot })),
362
+ ];
363
+ for (const { key, ref, slot } of wanted) {
364
+ if (!ref || typeof ref.id !== 'string' || typeof ref.version !== 'string')
365
+ throw new errors_1.KitStop('This video names a part it has no version for. Nothing was spent.', 'change_request');
366
+ if (lock) {
367
+ const entry = lock.parts[ref.id];
368
+ if (!entry || entry.version !== ref.version)
369
+ throw new errors_1.KitStop(`Part ${ref.id} ${ref.version} is not in this video’s parts list, so it can’t run. Nothing was spent.`, 'change_request');
370
+ const tooOld = (0, version_1.kitRangeRefusal)({ id: ref.id, version: ref.version, kit: entry.kit });
371
+ if (tooOld)
372
+ throw new errors_1.KitStop(`${tooOld} Nothing was spent.`, 'update_kit');
373
+ }
374
+ }
375
+ for (const { key, ref, slot } of wanted) {
376
+ let loaded;
377
+ try {
378
+ loaded = await this.deps.host.loader.load({ ref, lock, dev, home: this.deps.home, env: this.deps.env, signal: this.stop.signal });
379
+ }
380
+ catch (error) {
381
+ const words = `${error instanceof Error ? error.message : 'A part could not be loaded.'} Nothing was spent.`;
382
+ if (error instanceof loader_1.PartLoadError && error.code === 'kit_range')
383
+ throw new errors_1.KitStop(words, 'update_kit');
384
+ if (error instanceof loader_1.PartLoadError && error.code === 'unreachable')
385
+ throw new errors_1.KitStop(`${words} Run the same command again in a minute.`, 'stop');
386
+ throw new errors_1.KitStop(words, 'change_request');
387
+ }
388
+ const m = loaded.manifest;
389
+ if (m.id !== ref.id || m.version !== ref.version)
390
+ throw new errors_1.KitStop(`Part ${ref.id} ${ref.version} loaded as another version. Nothing was spent.`, 'change_request');
391
+ const refusal = (0, version_1.interfaceRefusal)(m);
392
+ if (refusal)
393
+ throw new errors_1.KitStop(`${refusal} Nothing was spent.`, 'update_kit');
394
+ if (slot && (m.layer !== slot || m.kind !== SLOT_KIND[slot]))
395
+ throw new errors_1.KitStop(`Part ${ref.id} can’t fill the ${slot} layer. Nothing was spent.`, 'change_request');
396
+ const lockModels = lock ? lock.parts[ref.id].models : m.needs.models;
397
+ const paid = m.needs.network || m.needs.models.length > 0 || m.kind.startsWith('generate_');
398
+ if (paid && (!Array.isArray(lockModels) || lockModels.length === 0))
399
+ throw new errors_1.KitStop(`Part ${ref.id} orders paid pieces but this video’s parts list allows it none. Nothing was spent.`, 'change_request');
400
+ const missing = this.missingNeeds(m.needs);
401
+ if (missing)
402
+ throw new errors_1.KitStop(`This computer can’t run part ${ref.id}: ${missing}. Run the computer check, then make the video again. Nothing was spent.`, 'refused');
403
+ try {
404
+ (0, schema_1.assertCheckable)(m.inputs);
405
+ (0, schema_1.assertCheckable)(m.outputs);
406
+ }
407
+ catch (error) {
408
+ throw new errors_1.KitStop(`Part ${ref.id} can’t be checked by this kit (${error.message}). Nothing was spent.`, 'update_kit');
409
+ }
410
+ this.parts.set(key, loaded);
411
+ }
412
+ }
413
+ missingNeeds(needs) {
414
+ if (needs.ffmpeg) {
415
+ if (!this.tools.ffmpeg.path || !this.tools.ffprobe.path)
416
+ return 'the video tools are missing';
417
+ if (typeof needs.ffmpeg === 'object') {
418
+ const lacking = [
419
+ ...(needs.ffmpeg.filters ?? []).filter((f) => !(this.tools.ffmpeg.filters ?? []).includes(f)),
420
+ ...(needs.ffmpeg.encoders ?? []).filter((e) => !(this.tools.ffmpeg.encoders ?? []).includes(e)),
421
+ ];
422
+ if (lacking.length)
423
+ return 'the video tools here lack something it needs';
424
+ }
425
+ }
426
+ if (needs.browser && !this.browserReady)
427
+ return 'the video browser is missing';
428
+ return null;
429
+ }
430
+ /** The timeline, then the layers that are on. Saved steps with the same hash are reused. */
431
+ async pipeline() {
432
+ const outputs = new Map();
433
+ const scope = { plan: this.planScope, brand: this.brand, steps: outputs, assets: this.style.assets };
434
+ const hashes = {};
435
+ let timeline = null;
436
+ const fixes = this.run.fixes ?? [];
437
+ const fixed = (id, inputs) => {
438
+ const mine = fixes.filter((f) => f.target === id);
439
+ return {
440
+ inputs: mine.reduce((acc, f) => ({ ...acc, ...f.inputs }), inputs),
441
+ round: mine.length ? Math.max(...mine.map((f) => f.round)) : undefined,
442
+ };
443
+ };
444
+ for (const step of this.style.style.timeline) {
445
+ const loaded = this.parts.get(step.id);
446
+ const { inputs, round } = fixed(step.id, (0, bind_1.bindInputs)(step.inputs, scope, step.id));
447
+ const out = await this.runStep({ id: step.id, ref: step.part, loaded, inputs, models: this.modelsOf(step.part, loaded), fix: round }, hashes);
448
+ outputs.set(step.id, out);
449
+ if (out.timeline && typeof out.timeline === 'object')
450
+ timeline = out.timeline;
451
+ }
452
+ const last = this.style.style.timeline[this.style.style.timeline.length - 1];
453
+ const made = outputs.get(last.id)?.video;
454
+ if (!(0, canonical_1.isFileRef)(made) || made.media !== 'video')
455
+ throw new errors_1.KitStop('The style’s last step made no video.', 'change_request');
456
+ let cut = made;
457
+ timeline ??= await this.bareTimeline(cut);
458
+ let words;
459
+ let captions;
460
+ let verdict = null;
461
+ for (const slot of this.layersOn()) {
462
+ const id = `layer-${slot}`;
463
+ const loaded = this.parts.get(id);
464
+ const ref = { id: loaded.manifest.id, version: loaded.manifest.version };
465
+ const { inputs, round } = fixed(id, { video: cut, timeline, brand: this.brand, expect: expectationOf(this.style.style, this.handed.plan), ...(words ? { words } : {}) });
466
+ const out = await this.runStep({ id, ref, loaded, inputs, models: this.modelsOf(ref, loaded), fix: round }, hashes);
467
+ if (slot === 'check') {
468
+ verdict = out.verdict;
469
+ continue;
470
+ }
471
+ const video = out.video;
472
+ if (!(0, canonical_1.isFileRef)(video) || video.media !== 'video')
473
+ throw new errors_1.KitStop(`The ${slot} layer made no video.`, 'change_request');
474
+ cut = video;
475
+ if (out.timeline && typeof out.timeline === 'object')
476
+ timeline = out.timeline;
477
+ if (slot === 'captions') {
478
+ if ((0, canonical_1.isFileRef)(out.words))
479
+ words = out.words;
480
+ if ((0, canonical_1.isFileRef)(out.captions))
481
+ captions = out.captions;
482
+ }
483
+ }
484
+ if (!verdict || typeof verdict.pass !== 'boolean' || !Array.isArray(verdict.checks))
485
+ throw new errors_1.KitStop('The final check gave no verdict.', 'change_request');
486
+ return { cut, captions, verdict, steps: hashes };
487
+ }
488
+ modelsOf(ref, loaded) {
489
+ return this.lock ? (this.lock.parts[ref.id]?.models ?? []) : loaded.manifest.needs.models;
490
+ }
491
+ async bareTimeline(cut) {
492
+ const probe = (0, toolchain_1.kitTools)({ ffmpeg: this.tools.ffmpeg.path ?? 'ffmpeg', ffprobe: this.tools.ffprobe.path ?? 'ffprobe', toolchain: this.toolchain }, this.stop.signal).probe;
493
+ const info = await probe(cut.path).catch(() => null);
494
+ const duration_s = info?.duration_s ?? cut.duration_s;
495
+ const width = info?.width ?? cut.width;
496
+ const height = info?.height ?? cut.height;
497
+ const fps = info?.fps ?? cut.fps;
498
+ if (!duration_s || !width || !height || !fps)
499
+ throw new errors_1.KitStop('The video’s length and size could not be read.', 'failed');
500
+ return { duration_s, width, height, fps, scenes: [], speech: [] };
501
+ }
502
+ /** One step: reused when its saved record has the same hash and intact files, else run (and retried once). */
503
+ async runStep(spec, hashes) {
504
+ const { manifest } = spec.loaded;
505
+ const inputErrors = (0, schema_1.schemaErrors)(manifest.inputs, spec.inputs, `${spec.id} inputs`);
506
+ if (inputErrors.length) {
507
+ this.deps.log.write('error', 'step inputs refused', { step: spec.id, errors: inputErrors });
508
+ throw new errors_1.KitStop(`The style gave its ${spec.id} step something its part can’t take, so the video stopped before that step.`, 'change_request');
509
+ }
510
+ const hash = (0, canonical_1.stepHash)({ interface: manifest.interface, part: spec.ref, toolchain: this.toolchain, inputs: spec.inputs, fix: spec.fix });
511
+ hashes[spec.id] = hash;
512
+ const saved = await this.store.readStep(spec.id);
513
+ if (saved && saved.status === 'done' && saved.step_hash === hash && saved.outputs && this.run.steps[spec.id]?.status === 'done') {
514
+ const files = fileRefsIn(saved.outputs);
515
+ if ((await Promise.all(files.map(files_1.intact))).every(Boolean)) {
516
+ this.book.finish(spec.id);
517
+ return saved.outputs;
518
+ }
519
+ }
520
+ const dir = this.store.stepDir(spec.id);
521
+ const workDir = path.join(dir, 'out');
522
+ // Scratch space sits inside the step's own folder, so a part can register a scratch file too.
523
+ const tmpDir = path.join(workDir, '.tmp');
524
+ for (const attempt of [1, 2]) {
525
+ if (this.stop.signal.aborted)
526
+ throw this.fatal ?? new errors_1.KitStop('This video was stopped.', 'stop');
527
+ await (0, promises_1.rm)(workDir, { recursive: true, force: true });
528
+ await (0, promises_1.rm)(tmpDir, { recursive: true, force: true });
529
+ await (0, promises_1.mkdir)(workDir, { recursive: true, mode: 0o700 });
530
+ await (0, promises_1.mkdir)(tmpDir, { recursive: true, mode: 0o700 });
531
+ const record = {
532
+ record: 1,
533
+ step: spec.id,
534
+ part: spec.ref,
535
+ step_hash: hash,
536
+ status: 'running',
537
+ inputs: (0, secrets_1.withoutSignedLinks)(spec.inputs),
538
+ pieces: [],
539
+ started_at: this.now().toISOString(),
540
+ };
541
+ await this.store.writeStep(record);
542
+ this.run.steps[spec.id] = { status: 'running', step_hash: hash };
543
+ await this.saveRun();
544
+ this.book.start(spec.id);
545
+ this.deps.log.write('info', 'step started', { step: spec.id, part: `${spec.ref.id}@${spec.ref.version}`, attempt });
546
+ void this.reporter.send();
547
+ const timeoutMs = manifest.timing.timeout_s * 1000;
548
+ const link = (0, client_1.linkedSignal)([this.stop.signal], timeoutMs);
549
+ try {
550
+ const ctx = this.context(spec, attempt, workDir, tmpDir, link.signal, record);
551
+ const outputs = await new Promise((resolve, reject) => {
552
+ const ended = () => reject(this.stop.signal.aborted ? new errors_1.PartError('stopped') : new errors_1.PartError('timeout', `${spec.id} ran past ${manifest.timing.timeout_s} s`));
553
+ // Stopped while the step was being saved: the part never starts.
554
+ if (link.signal.aborted)
555
+ return ended();
556
+ link.signal.addEventListener('abort', ended, { once: true });
557
+ spec.loaded.run(spec.inputs, ctx).then((o) => resolve(o), reject);
558
+ });
559
+ if (this.fatal)
560
+ throw this.fatal;
561
+ const outputErrors = outputs && typeof outputs === 'object' ? (0, schema_1.schemaErrors)(manifest.outputs, outputs, `${spec.id} outputs`) : ['outputs are not an object'];
562
+ if (outputErrors.length) {
563
+ this.deps.log.write('error', 'step outputs refused', { step: spec.id, errors: outputErrors });
564
+ throw new errors_1.PartError('output_invalid', outputErrors.join('; '));
565
+ }
566
+ await this.checkOutputFiles(outputs, spec.loaded.dir, tmpDir);
567
+ record.status = 'done';
568
+ record.outputs = outputs;
569
+ record.finished_at = this.now().toISOString();
570
+ await this.store.writeStep({ ...record, outputs: outputs });
571
+ this.run.steps[spec.id] = { status: 'done', step_hash: hash, finished_at: record.finished_at };
572
+ await this.saveRun();
573
+ await (0, promises_1.rm)(tmpDir, { recursive: true, force: true });
574
+ this.book.finish(spec.id);
575
+ this.deps.log.write('info', 'step done', { step: spec.id, pieces: record.pieces.length });
576
+ void this.reporter.send(true);
577
+ return outputs;
578
+ }
579
+ catch (error) {
580
+ const stopped = this.fatal ?? (error instanceof errors_1.KitStop ? error : null);
581
+ const failure = stopped ? null : (0, errors_1.asPartError)(error);
582
+ record.status = stopped ? 'stopped' : 'failed';
583
+ if (failure)
584
+ record.error = { code: failure.code, ...(failure.detail ? { detail: this.deps.log.clean(failure.detail).slice(0, 2000) } : {}) };
585
+ record.finished_at = this.now().toISOString();
586
+ await this.store.writeStep(record);
587
+ this.run.steps[spec.id] = { status: record.status, step_hash: hash };
588
+ await this.saveRun();
589
+ if (stopped)
590
+ throw stopped;
591
+ this.deps.log.write('warn', 'step failed', { step: spec.id, attempt, code: failure.code });
592
+ if (failure.retryable && attempt === 1 && manifest.retry?.transient !== 0)
593
+ continue;
594
+ this.book.finish(spec.id, 'failed');
595
+ void this.reporter.send(true);
596
+ throw new errors_1.KitStop(failureWords(failure), failure.code === 'over_quote' ? 'stop' : 'failed', failure.code);
597
+ }
598
+ finally {
599
+ link.done();
600
+ }
601
+ }
602
+ throw new errors_1.KitStop(failureWords(new errors_1.PartError('tool_failed')), 'failed');
603
+ }
604
+ /** A part's output files must be in this video's folder (or the part's own) and unchanged. */
605
+ async checkOutputFiles(outputs, partDir, tmpDir) {
606
+ for (const ref of fileRefsIn(outputs)) {
607
+ if (!(0, paths_1.isInside)(this.layout.root, ref.path) && !(0, paths_1.isInside)(partDir, ref.path))
608
+ throw new errors_1.PartError('output_invalid', 'an output file is outside this video’s folder');
609
+ if ((0, paths_1.isInside)(tmpDir, ref.path))
610
+ throw new errors_1.PartError('output_invalid', 'an output file is in the step’s scratch folder, which is cleared after the step');
611
+ // Every output is hashed again once the part returns, even one it registered.
612
+ const actual = await (0, files_1.hashFile)(ref.path).catch(() => null);
613
+ if (!actual || actual.sha256 !== ref.sha256 || actual.bytes !== ref.bytes)
614
+ throw new errors_1.PartError('output_invalid', 'an output file does not match its hash');
615
+ }
616
+ }
617
+ context(spec, attempt, workDir, tmpDir, signal, record) {
618
+ const { manifest } = spec.loaded;
619
+ const tools = (0, toolchain_1.kitTools)({ ffmpeg: this.tools.ffmpeg.path ?? '', ffprobe: this.tools.ffprobe.path ?? '', toolchain: this.toolchain }, signal);
620
+ const seedInput = spec.inputs.seed ?? null;
621
+ const order = manifest.needs.network
622
+ ? (0, pieces_1.pieceOrderer)({
623
+ line: this.deps.line,
624
+ videoId: this.videoId,
625
+ stepId: spec.id,
626
+ part: spec.ref,
627
+ models: spec.models,
628
+ workDir,
629
+ runRoot: this.layout.root,
630
+ cache: this.cache,
631
+ signal,
632
+ sleep: this.sleep,
633
+ probe: tools.probe,
634
+ hosted: this.hosted,
635
+ locks: this.pieceLocks,
636
+ onWait: () => void this.reporter.send(),
637
+ onPiece: async (piece) => {
638
+ record.pieces.push(piece);
639
+ await this.store.writeStep(record);
640
+ this.book.count(spec.id, { done: record.pieces.length });
641
+ void this.reporter.send(true);
642
+ },
643
+ })
644
+ : null;
645
+ const style = { id: this.style.style.id, version: this.style.style.version };
646
+ const ctx = {
647
+ interface: 1,
648
+ video: { id: this.videoId, style, env: this.deps.environment },
649
+ step: { id: spec.id, attempt },
650
+ part: { id: spec.ref.id, version: spec.ref.version, dir: spec.loaded.dir },
651
+ workDir,
652
+ tmpDir,
653
+ seed: (piece) => (0, canonical_1.pieceSeed)({ step: spec.id, part: spec.ref.id, piece, seed: seedInput }),
654
+ ...(order
655
+ ? {
656
+ line: {
657
+ order: async (request) => {
658
+ try {
659
+ return await order(request);
660
+ }
661
+ catch (error) {
662
+ if (error instanceof errors_1.KitStop)
663
+ this.halt(error);
664
+ throw error;
665
+ }
666
+ },
667
+ },
668
+ }
669
+ : {}),
670
+ tools,
671
+ ...(manifest.needs.browser ? { browser: this.deps.host.browser.provider({ allowDirs: [spec.loaded.dir, this.layout.root], signal }) } : {}),
672
+ log: this.deps.log.forStep(spec.id),
673
+ progress: (update) => {
674
+ this.book.count(spec.id, update ?? {});
675
+ void this.reporter.send();
676
+ },
677
+ file: async (relativePath, media) => {
678
+ const target = path.resolve(workDir, relativePath);
679
+ if (!(0, paths_1.isInside)(workDir, target))
680
+ throw new errors_1.PartError('bad_input', 'a part may register only files in its own folder');
681
+ return (0, files_1.fileRef)(target, media, tools.probe);
682
+ },
683
+ error: (code, detail) => new errors_1.PartError(code, detail),
684
+ signal,
685
+ };
686
+ return ctx;
687
+ }
688
+ /** The one fix: the check's own hint, or the server's check mapped to the layer that can redo it. */
689
+ fixFrom(verdict, serverChecks, from) {
690
+ const round = (this.run.fixes?.length ?? 0) + 1;
691
+ const failed = verdict.checks.filter((c) => c.status === 'fail' && (serverChecks.length === 0 || serverChecks.includes(c.code)));
692
+ const hinted = failed.find((c) => c.fix)?.fix;
693
+ if (hinted?.slot && this.parts.has(`layer-${hinted.slot}`))
694
+ return { target: `layer-${hinted.slot}`, inputs: hinted.inputs ?? {}, round, from };
695
+ if (hinted?.step && this.parts.has(hinted.step))
696
+ return { target: hinted.step, inputs: hinted.inputs ?? {}, round, from };
697
+ for (const check of serverChecks) {
698
+ const slot = SERVER_CHECK_SLOT[check];
699
+ if (slot && this.parts.has(`layer-${slot}`))
700
+ return { target: `layer-${slot}`, inputs: {}, round, from };
701
+ }
702
+ return null;
703
+ }
704
+ async checkFailed(verdict) {
705
+ await this.writeFinal(null, verdict);
706
+ this.book.finish('layer-check', 'failed');
707
+ this.book.note = 'The final check found a problem';
708
+ await this.reporter.send(true);
709
+ this.run.status = 'failed';
710
+ await this.saveRun();
711
+ return { status: 'failed', message: 'The video didn’t pass the final check, so it wasn’t sent. Nothing more will be charged for it.' };
712
+ }
713
+ async writeFinal(result, verdict) {
714
+ await (0, promises_1.mkdir)(this.layout.final, { recursive: true, mode: 0o700 });
715
+ await (0, save_1.writeJson)(path.join(this.layout.final, 'check.json'), verdict);
716
+ if (!result)
717
+ return null;
718
+ const final = path.join(this.layout.final, 'final.mp4');
719
+ await (0, promises_1.copyFile)(result.cut.path, final);
720
+ if (result.captions)
721
+ await (0, promises_1.copyFile)(result.captions.path, path.join(this.layout.final, 'captions.vtt'));
722
+ return final;
723
+ }
724
+ manifestFor(steps) {
725
+ const parts = {};
726
+ for (const loaded of this.parts.values())
727
+ parts[loaded.manifest.id] = loaded.manifest.version;
728
+ return {
729
+ style: { id: this.style.style.id, version: this.style.style.version },
730
+ lock_sha256: this.run.lock_sha256 || null,
731
+ parts,
732
+ steps,
733
+ kit: { version: version_1.KIT_VERSION, toolchain: this.toolchain },
734
+ parts_source: this.run.kit.parts_source,
735
+ };
736
+ }
737
+ /** Upload, our server's check, and one fix after a first failed check. */
738
+ async upload(result) {
739
+ let current = result;
740
+ for (;;) {
741
+ const final = (await this.writeFinal(current, current.verdict));
742
+ const data = await (0, promises_1.readFile)(final);
743
+ if (data.length > types_1.MAX_UPLOAD_BYTES)
744
+ throw new errors_1.KitStop('The finished video is larger than the line takes.', 'failed');
745
+ const sha256 = (0, canonical_1.sha256Hex)(data);
746
+ // Only the exact bytes the final check passed are sent.
747
+ if (sha256 !== current.cut.sha256 || data.length !== current.cut.bytes) {
748
+ throw new errors_1.KitStop('The finished video changed after its final check, so it wasn’t sent. Run the same command again to make it again.', 'failed');
749
+ }
750
+ let captions_vtt;
751
+ if (current.captions) {
752
+ const captions = await (0, promises_1.readFile)(current.captions.path);
753
+ if ((0, canonical_1.sha256Hex)(captions) !== current.captions.sha256 || captions.length !== current.captions.bytes) {
754
+ throw new errors_1.KitStop('The captions changed after the final check, so the video wasn’t sent. Run the same command again to make it again.', 'failed');
755
+ }
756
+ if (captions.length > types_1.MAX_CAPTIONS_BYTES)
757
+ throw new errors_1.KitStop('The captions file is larger than the line takes.', 'failed');
758
+ captions_vtt = captions.toString('utf8');
759
+ }
760
+ this.book.note = 'Sending your video for its final check';
761
+ this.run.status = 'uploading';
762
+ await this.saveRun();
763
+ void this.reporter.send(true);
764
+ let check;
765
+ try {
766
+ check = await this.sendUpload(sha256, data, captions_vtt, this.manifestFor(current.steps));
767
+ }
768
+ catch (error) {
769
+ if (error instanceof client_1.LineError)
770
+ throw new errors_1.KitStop(lineWords(error), error.next === 'update_kit' ? 'update_kit' : error.next === 'change_request' ? 'change_request' : 'stop', error.code);
771
+ throw error;
772
+ }
773
+ this.run.upload_attempt = check.attempt;
774
+ if (check.result === 'pass') {
775
+ this.run.status = 'done';
776
+ await this.saveRun();
777
+ await this.cleanTmp();
778
+ return { status: 'done', message: `Your video is ready. It passed the final check. Saved here: ${final}` };
779
+ }
780
+ const reasons = check.reasons.map((r) => r.message).filter(Boolean).join(' ');
781
+ const serverFixUsed = (this.run.fixes ?? []).some((f) => f.from === 'server');
782
+ const fix = check.fixes_left > 0 && !serverFixUsed ? this.fixFrom(current.verdict, check.reasons.map((r) => r.check), 'server') : null;
783
+ if (!fix) {
784
+ this.run.status = 'failed';
785
+ await this.saveRun();
786
+ return { status: 'failed', message: `The video didn’t pass our check. ${reasons}`.trim() };
787
+ }
788
+ this.run.fixes = [...(this.run.fixes ?? []), fix];
789
+ this.run.status = 'running';
790
+ await this.saveRun();
791
+ this.reporter.resume();
792
+ this.say('Our check found something to fix. Fixing it once…');
793
+ current = await this.pipeline();
794
+ if (!current.verdict.pass)
795
+ return this.checkFailed(current.verdict);
796
+ // A fix that changed nothing is not sent again: the same bytes get the same answer.
797
+ }
798
+ }
799
+ /**
800
+ * The slot, the PUT and done, saved stage by stage. These same bytes already
801
+ * put (or already checked) are never sent again: done is asked again for
802
+ * that upload, which gives the same answer.
803
+ */
804
+ async sendUpload(sha256, data, captions_vtt, manifest) {
805
+ const { line } = this.deps;
806
+ const saved = (await this.store.readUpload()) ?? { uploads: [] };
807
+ const finish = async (stage) => {
808
+ this.book.note = 'Checking your video';
809
+ this.run.status = 'checking';
810
+ await this.saveRun();
811
+ await this.reporter.flush();
812
+ this.reporter.pause();
813
+ const check = await line.finishUpload(this.videoId, stage.upload_id, this.stop.signal);
814
+ stage.stage = 'done';
815
+ stage.result = check.result;
816
+ await this.store.writeUpload(saved);
817
+ return check;
818
+ };
819
+ // Bytes sent before (or maybe sent: a crash can land between the PUT and
820
+ // the journal) are asked about first; they are sent again only when the
821
+ // line says the slot holds no such file.
822
+ const sent = [...saved.uploads].reverse().find((u) => u.sha256 === sha256 && u.quote_id === this.handed.quote_id);
823
+ if (sent) {
824
+ try {
825
+ return await finish(sent);
826
+ }
827
+ catch (error) {
828
+ if (!(sent.stage === 'requested' && error instanceof client_1.LineError && error.code === 'file_mismatch'))
829
+ throw error;
830
+ this.reporter.resume();
831
+ }
832
+ }
833
+ const slot = await line.openUpload(this.videoId, { sha256, bytes: data.length, content_type: 'video/mp4', ...(captions_vtt ? { captions_vtt } : {}), manifest }, this.stop.signal);
834
+ const stage = { upload_id: slot.upload_id, attempt: slot.attempt, sha256, quote_id: this.handed.quote_id, stage: 'requested' };
835
+ saved.uploads.push(stage);
836
+ await this.store.writeUpload(saved);
837
+ await line.put(slot.put, data, this.stop.signal);
838
+ stage.stage = 'put';
839
+ await this.store.writeUpload(saved);
840
+ return finish(stage);
841
+ }
842
+ async cleanTmp() {
843
+ for (const key of this.parts.keys())
844
+ await (0, promises_1.rm)(path.join(this.store.stepDir(key), 'out', '.tmp'), { recursive: true, force: true }).catch(() => undefined);
845
+ }
846
+ }
847
+ async function makeVideo(videoId, deps) {
848
+ return new Maker(videoId, deps).make();
849
+ }
850
+ //# sourceMappingURL=run.js.map