@mevdragon/vidfarm-devcli 0.2.11 → 0.2.13

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 (199) hide show
  1. package/.env.example +37 -1
  2. package/README.md +27 -183
  3. package/SKILL.director.md +293 -0
  4. package/SKILL.platform.md +312 -0
  5. package/auto-create-hyperframe-templates/AUTO_CREATE_HYPERFRAME_TEMPLATES.md +275 -0
  6. package/auto-create-hyperframe-templates/extractor-system-prompt.md +128 -0
  7. package/auto-create-hyperframe-templates/input-manifest.schema.json +56 -0
  8. package/auto-create-hyperframe-templates/lambda-memory-estimator.md +118 -0
  9. package/auto-create-hyperframe-templates/production-graph.schema.json +314 -0
  10. package/auto-create-hyperframe-templates/runbook.md +198 -0
  11. package/auto-create-hyperframe-templates/scripts/create-hyperframe-template.mjs +368 -0
  12. package/auto-create-hyperframe-templates/template-plan.schema.json +311 -0
  13. package/auto-create-hyperframe-templates/template-planner-prompt.md +144 -0
  14. package/auto-create-templates/AUTO_CREATE_TEMPLATES.md +124 -0
  15. package/auto-create-templates/extractor-system-prompt.md +8 -2
  16. package/auto-create-templates/template-plan.schema.json +73 -2
  17. package/auto-create-templates/template-planner-prompt.md +63 -1
  18. package/dist/src/account-pages-legacy.js +361 -89
  19. package/dist/src/account-pages-legacy.js.map +1 -0
  20. package/dist/src/account-pages.js +13 -3
  21. package/dist/src/account-pages.js.map +1 -0
  22. package/dist/src/app.js +7060 -946
  23. package/dist/src/app.js.map +1 -0
  24. package/dist/src/cli.js +111 -29
  25. package/dist/src/cli.js.map +1 -0
  26. package/dist/src/composition-runtime.js +581 -0
  27. package/dist/src/composition-runtime.js.map +1 -0
  28. package/dist/src/config.js +69 -11
  29. package/dist/src/config.js.map +1 -0
  30. package/dist/src/context.js +280 -21
  31. package/dist/src/context.js.map +1 -0
  32. package/dist/src/dev-app-legacy.js +18 -17
  33. package/dist/src/dev-app-legacy.js.map +1 -0
  34. package/dist/src/dev-app.js +1 -0
  35. package/dist/src/dev-app.js.map +1 -0
  36. package/dist/src/domain.js +2 -1
  37. package/dist/src/domain.js.map +1 -0
  38. package/dist/src/editor-chat-history.js +10 -0
  39. package/dist/src/editor-chat-history.js.map +1 -0
  40. package/dist/src/editor-chat.js +133 -24
  41. package/dist/src/editor-chat.js.map +1 -0
  42. package/dist/src/frontend/flockposter-cache-store.js +10 -2
  43. package/dist/src/frontend/flockposter-cache-store.js.map +1 -0
  44. package/dist/src/frontend/homepage-client.js +6 -3
  45. package/dist/src/frontend/homepage-client.js.map +1 -0
  46. package/dist/src/frontend/homepage-shared.js +1 -0
  47. package/dist/src/frontend/homepage-shared.js.map +1 -0
  48. package/dist/src/frontend/homepage-store.js +1 -0
  49. package/dist/src/frontend/homepage-store.js.map +1 -0
  50. package/dist/src/frontend/homepage-view.js +131 -18
  51. package/dist/src/frontend/homepage-view.js.map +1 -0
  52. package/dist/src/frontend/page-runtime-client.js +5 -2
  53. package/dist/src/frontend/page-runtime-client.js.map +1 -0
  54. package/dist/src/frontend/page-runtime-store.js +1 -0
  55. package/dist/src/frontend/page-runtime-store.js.map +1 -0
  56. package/dist/src/frontend/sentry.js +42 -0
  57. package/dist/src/frontend/sentry.js.map +1 -0
  58. package/dist/src/frontend/template-editor-chat.js +1719 -213
  59. package/dist/src/frontend/template-editor-chat.js.map +1 -0
  60. package/dist/src/help-page.js +333 -0
  61. package/dist/src/help-page.js.map +1 -0
  62. package/dist/src/homepage.js +169 -47
  63. package/dist/src/homepage.js.map +1 -0
  64. package/dist/src/hyperframes/composition.js +180 -0
  65. package/dist/src/hyperframes/composition.js.map +1 -0
  66. package/dist/src/index.js +1 -0
  67. package/dist/src/index.js.map +1 -0
  68. package/dist/src/instrument.js +30 -0
  69. package/dist/src/instrument.js.map +1 -0
  70. package/dist/src/lib/crypto.js +1 -0
  71. package/dist/src/lib/crypto.js.map +1 -0
  72. package/dist/src/lib/dev-log.js +54 -0
  73. package/dist/src/lib/dev-log.js.map +1 -0
  74. package/dist/src/lib/display-name.js +11 -0
  75. package/dist/src/lib/display-name.js.map +1 -0
  76. package/dist/src/lib/ids.js +21 -1
  77. package/dist/src/lib/ids.js.map +1 -0
  78. package/dist/src/lib/images.js +1 -0
  79. package/dist/src/lib/images.js.map +1 -0
  80. package/dist/src/lib/json.js +1 -0
  81. package/dist/src/lib/json.js.map +1 -0
  82. package/dist/src/lib/template-dna.js +1 -0
  83. package/dist/src/lib/template-dna.js.map +1 -0
  84. package/dist/src/lib/template-paths.js +1 -0
  85. package/dist/src/lib/template-paths.js.map +1 -0
  86. package/dist/src/lib/template-style-options.js +40 -2
  87. package/dist/src/lib/template-style-options.js.map +1 -0
  88. package/dist/src/lib/time.js +1 -0
  89. package/dist/src/lib/time.js.map +1 -0
  90. package/dist/src/lib/video-quality-harness.js +60 -0
  91. package/dist/src/lib/video-quality-harness.js.map +1 -0
  92. package/dist/src/page-runtime.js +1 -0
  93. package/dist/src/page-runtime.js.map +1 -0
  94. package/dist/src/page-shell.js +1501 -49
  95. package/dist/src/page-shell.js.map +1 -0
  96. package/dist/src/primitive-context.js +144 -8
  97. package/dist/src/primitive-context.js.map +1 -0
  98. package/dist/src/primitive-registry.js +1919 -78
  99. package/dist/src/primitive-registry.js.map +1 -0
  100. package/dist/src/primitive-sdk.js +1 -0
  101. package/dist/src/primitive-sdk.js.map +1 -0
  102. package/dist/src/primitives/remotion/html-image.js +3 -1
  103. package/dist/src/primitives/remotion/html-image.js.map +1 -0
  104. package/dist/src/primitives/remotion/media-slideshow.js +60 -0
  105. package/dist/src/primitives/remotion/media-slideshow.js.map +1 -0
  106. package/dist/src/react-page-shell.js +1 -0
  107. package/dist/src/react-page-shell.js.map +1 -0
  108. package/dist/src/ready-post-schedule-component.js +1 -0
  109. package/dist/src/ready-post-schedule-component.js.map +1 -0
  110. package/dist/src/registry.js +237 -24
  111. package/dist/src/registry.js.map +1 -0
  112. package/dist/src/runtime.js +3 -0
  113. package/dist/src/runtime.js.map +1 -0
  114. package/dist/src/services/api-call-history.js +4 -0
  115. package/dist/src/services/api-call-history.js.map +1 -0
  116. package/dist/src/services/auth.js +40 -50
  117. package/dist/src/services/auth.js.map +1 -0
  118. package/dist/src/services/billing.js +20 -44
  119. package/dist/src/services/billing.js.map +1 -0
  120. package/dist/src/services/chat-threads.js +10 -6
  121. package/dist/src/services/chat-threads.js.map +1 -0
  122. package/dist/src/services/fork-access.js +94 -0
  123. package/dist/src/services/fork-access.js.map +1 -0
  124. package/dist/src/services/ghostcut.js +132 -0
  125. package/dist/src/services/ghostcut.js.map +1 -0
  126. package/dist/src/services/hyperframes.js +1014 -0
  127. package/dist/src/services/hyperframes.js.map +1 -0
  128. package/dist/src/services/job-capacity.js +14 -0
  129. package/dist/src/services/job-capacity.js.map +1 -0
  130. package/dist/src/services/job-logs.js +4 -0
  131. package/dist/src/services/job-logs.js.map +1 -0
  132. package/dist/src/services/jobs.js +99 -91
  133. package/dist/src/services/jobs.js.map +1 -0
  134. package/dist/src/services/media-processing.js +743 -0
  135. package/dist/src/services/media-processing.js.map +1 -0
  136. package/dist/src/services/primitive-media-lambda.js +280 -0
  137. package/dist/src/services/primitive-media-lambda.js.map +1 -0
  138. package/dist/src/services/providers.js +1560 -178
  139. package/dist/src/services/providers.js.map +1 -0
  140. package/dist/src/services/rate-limits.js +3 -2
  141. package/dist/src/services/rate-limits.js.map +1 -0
  142. package/dist/src/services/remotion.js +495 -92
  143. package/dist/src/services/remotion.js.map +1 -0
  144. package/dist/src/services/serverless-auth.js +374 -0
  145. package/dist/src/services/serverless-auth.js.map +1 -0
  146. package/dist/src/services/serverless-jobs.js +1074 -0
  147. package/dist/src/services/serverless-jobs.js.map +1 -0
  148. package/dist/src/services/serverless-provider-keys.js +401 -0
  149. package/dist/src/services/serverless-provider-keys.js.map +1 -0
  150. package/dist/src/services/serverless-records.js +1086 -0
  151. package/dist/src/services/serverless-records.js.map +1 -0
  152. package/dist/src/services/serverless-template-configs.js +67 -0
  153. package/dist/src/services/serverless-template-configs.js.map +1 -0
  154. package/dist/src/services/storage.js +170 -35
  155. package/dist/src/services/storage.js.map +1 -0
  156. package/dist/src/services/template-certification.js +295 -3
  157. package/dist/src/services/template-certification.js.map +1 -0
  158. package/dist/src/services/template-loader.js +45 -1
  159. package/dist/src/services/template-loader.js.map +1 -0
  160. package/dist/src/services/template-runtime-bundles.js +217 -0
  161. package/dist/src/services/template-runtime-bundles.js.map +1 -0
  162. package/dist/src/services/template-sources.js +452 -87
  163. package/dist/src/services/template-sources.js.map +1 -0
  164. package/dist/src/services/video-normalization.js +2 -0
  165. package/dist/src/services/video-normalization.js.map +1 -0
  166. package/dist/src/services/webhooks.js +7 -6
  167. package/dist/src/services/webhooks.js.map +1 -0
  168. package/dist/src/template-editor-pages.js +5637 -1322
  169. package/dist/src/template-editor-pages.js.map +1 -0
  170. package/dist/src/template-editor-shell.js +1376 -170
  171. package/dist/src/template-editor-shell.js.map +1 -0
  172. package/dist/src/template-sdk.js +1 -0
  173. package/dist/src/template-sdk.js.map +1 -0
  174. package/dist/src/worker.js +11 -226
  175. package/dist/src/worker.js.map +1 -0
  176. package/package.json +47 -12
  177. package/public/assets/homepage-client-app.js +34 -8
  178. package/public/assets/page-runtime-client-app.js +44 -18
  179. package/readme.secret.md +89 -0
  180. package/templates/vidfarm_template_0000/README.md +106 -0
  181. package/templates/vidfarm_template_0000/SKILL.md +266 -0
  182. package/templates/vidfarm_template_0000/assets/Abel-Regular.ttf +0 -0
  183. package/templates/vidfarm_template_0000/assets/DMSerifDisplay-Regular.ttf +0 -0
  184. package/templates/vidfarm_template_0000/assets/Montserrat[wght].ttf +0 -0
  185. package/templates/vidfarm_template_0000/assets/SourceCodePro[wght].ttf +0 -0
  186. package/templates/vidfarm_template_0000/assets/TikTokSans-SemiBold.ttf +0 -0
  187. package/templates/vidfarm_template_0000/assets/Yesteryear-Regular.ttf +0 -0
  188. package/templates/vidfarm_template_0000/composition.json +11 -0
  189. package/templates/vidfarm_template_0000/src/lib/images.js +221 -0
  190. package/templates/vidfarm_template_0000/src/remotion/Root.js +33 -0
  191. package/templates/vidfarm_template_0000/src/remotion/index.js +3 -0
  192. package/templates/vidfarm_template_0000/src/sdk.js +3 -0
  193. package/templates/vidfarm_template_0000/src/style-options.js +200 -0
  194. package/templates/vidfarm_template_0000/src/template-dna.js +9 -0
  195. package/templates/vidfarm_template_0000/src/template.js +1566 -0
  196. package/templates/vidfarm_template_0000/template.config.json +21 -0
  197. package/GETTING_STARTED.developers.md +0 -87
  198. package/SKILL.developer.md +0 -1177
  199. package/dist/src/db.js +0 -2007
@@ -7,12 +7,22 @@ import { config } from "../config.js";
7
7
  import { createId } from "../lib/ids.js";
8
8
  const REQUIRED_VIDEO_FPS = 30;
9
9
  const RENDER_MARKUP_MULTIPLIER = 1.2;
10
+ const RENDER_ASSET_PREFLIGHT_TIMEOUT_MS = 10_000;
11
+ const RENDER_ASSET_PREFLIGHT_MAX_FAILURES = 5;
12
+ const ABSOLUTE_MAX_REMOTE_VIDEO_DURATION_SECONDS = 10 * 60;
13
+ const ABSOLUTE_MAX_DIRECT_REMOTE_VIDEO_BYTES = 50 * 1024 * 1024;
14
+ const REMOTE_VIDEO_DOWNLOAD_BUDGET_BYTES = 1250 * 1024 * 1024;
15
+ const DEFAULT_PARALLEL_LAMBDA_TARGET = 6;
16
+ const MAX_PARALLEL_LAMBDA_TARGET = 24;
17
+ const SMALL_REMOTE_VIDEO_BYTES = 12 * 1024 * 1024;
18
+ const MEDIUM_REMOTE_VIDEO_BYTES = 25 * 1024 * 1024;
10
19
  export class RemotionService {
11
20
  async render(input) {
12
21
  const fps = input.fps ?? REQUIRED_VIDEO_FPS;
13
22
  if (fps !== REQUIRED_VIDEO_FPS) {
14
23
  throw new Error(`Vidfarm renders must be ${REQUIRED_VIDEO_FPS}fps. Received ${fps}fps.`);
15
24
  }
25
+ const preflightAssets = await preflightRenderAssets(input.preflightUrls ?? collectPreflightUrls(input.inputProps), { requireRemoteHttpUrls: Boolean(input.preflightUrls) });
16
26
  const serveUrl = nonBlank(input.serveUrl) ??
17
27
  nonBlank(config.REMOTION_SERVE_URL) ??
18
28
  inferHostedServeUrl(input.compositionId);
@@ -58,69 +68,89 @@ export class RemotionService {
58
68
  const hostedServeUrl = serveUrl;
59
69
  const hostedFunctionName = nonBlank(config.REMOTION_FUNCTION_NAME);
60
70
  const hostedBucketName = nonBlank(config.REMOTION_BUCKET_NAME);
61
- await this.assertCompositionFps({
71
+ const requestedFramesPerLambda = Number.isFinite(input.framesPerLambda)
72
+ ? Math.max(1, Math.floor(input.framesPerLambda))
73
+ : 40;
74
+ const composition = await this.resolveComposition({
62
75
  compositionId: input.compositionId,
63
- serveUrl: hostedServeUrl,
76
+ source: hostedServeUrl,
64
77
  inputProps: input.inputProps,
78
+ });
79
+ this.assertExpectedFps({
80
+ compositionId: input.compositionId,
81
+ actualFps: composition.fps,
65
82
  expectedFps: fps
66
83
  });
67
- return withRemotionCredentials(async () => {
68
- const render = await renderMediaOnLambda({
84
+ const renderPolicy = planLambdaRenderPolicy({
85
+ inputProps: input.inputProps,
86
+ preflightAssets,
87
+ requestedFramesPerLambda,
88
+ durationInFrames: composition.durationInFrames,
89
+ fps
90
+ });
91
+ const framesPerLambda = renderPolicy.framesPerLambda;
92
+ const render = await renderMediaOnLambda({
93
+ region: config.REMOTION_REGION,
94
+ functionName: hostedFunctionName,
95
+ serveUrl: hostedServeUrl,
96
+ composition: input.compositionId,
97
+ inputProps: input.inputProps,
98
+ codec: "h264",
99
+ privacy: "public",
100
+ forceBucketName: hostedBucketName,
101
+ forceWidth: 1080,
102
+ forceHeight: 1920,
103
+ framesPerLambda,
104
+ crf: 18,
105
+ overwrite: true
106
+ });
107
+ const startedAt = Date.now();
108
+ for (;;) {
109
+ const progress = await getRenderProgress({
69
110
  region: config.REMOTION_REGION,
70
111
  functionName: hostedFunctionName,
71
- serveUrl: hostedServeUrl,
72
- composition: input.compositionId,
73
- inputProps: input.inputProps,
74
- codec: "h264",
75
- privacy: "public",
76
- forceBucketName: hostedBucketName,
77
- forceWidth: 1080,
78
- forceHeight: 1920,
79
- framesPerLambda: 40,
80
- crf: 18,
81
- overwrite: true
112
+ bucketName: hostedBucketName,
113
+ renderId: render.renderId
82
114
  });
83
- const startedAt = Date.now();
84
- for (;;) {
85
- const progress = await getRenderProgress({
86
- region: config.REMOTION_REGION,
87
- functionName: hostedFunctionName,
88
- bucketName: hostedBucketName,
89
- renderId: render.renderId
90
- });
91
- if (progress.done) {
92
- if (!progress.outputFile) {
93
- throw new Error("Cloud render completed without an output file.");
94
- }
95
- const estimatedCostUsd = progress.costs.accruedSoFar;
96
- const chargeUsd = roundUsd(estimatedCostUsd * RENDER_MARKUP_MULTIPLIER);
97
- return {
98
- renderId: render.renderId,
99
- outputUrl: progress.outputFile,
100
- metadata: {
101
- mode: "lambda",
102
- bucketName: render.bucketName,
103
- compositionId: input.compositionId,
104
- fps,
105
- serveUrl,
106
- estimatedCostUsd,
107
- chargeUsd,
108
- costDisplay: progress.costs.displayCost,
109
- costCurrency: progress.costs.currency,
110
- costDisclaimer: progress.costs.disclaimer,
111
- estimatedBillingDurationInMilliseconds: progress.estimatedBillingDurationInMilliseconds,
112
- lambdasInvoked: progress.lambdasInvoked,
113
- overallProgress: progress.overallProgress,
114
- renderSize: progress.renderSize
115
- }
116
- };
117
- }
118
- if (Date.now() - startedAt > 10 * 60 * 1000) {
119
- throw new Error("Timed out waiting for cloud render output.");
115
+ const fatalError = summarizeFatalRenderError(progress);
116
+ if (fatalError) {
117
+ throw new Error(`Cloud render ${render.renderId} failed: ${fatalError}`);
118
+ }
119
+ if (progress.done) {
120
+ if (!progress.outputFile) {
121
+ throw new Error(`Cloud render ${render.renderId} completed without an output file.`);
120
122
  }
121
- await new Promise((resolve) => setTimeout(resolve, 5000));
123
+ const estimatedCostUsd = progress.costs.accruedSoFar;
124
+ const chargeUsd = roundUsd(estimatedCostUsd * RENDER_MARKUP_MULTIPLIER);
125
+ return {
126
+ renderId: render.renderId,
127
+ outputUrl: progress.outputFile,
128
+ metadata: {
129
+ mode: "lambda",
130
+ bucketName: render.bucketName,
131
+ compositionId: input.compositionId,
132
+ fps,
133
+ framesPerLambda,
134
+ estimatedLambdaCount: renderPolicy.estimatedLambdaCount,
135
+ mediaPolicy: renderPolicy,
136
+ serveUrl,
137
+ estimatedCostUsd,
138
+ chargeUsd,
139
+ costDisplay: progress.costs.displayCost,
140
+ costCurrency: progress.costs.currency,
141
+ costDisclaimer: progress.costs.disclaimer,
142
+ estimatedBillingDurationInMilliseconds: progress.estimatedBillingDurationInMilliseconds,
143
+ lambdasInvoked: progress.lambdasInvoked,
144
+ overallProgress: progress.overallProgress,
145
+ renderSize: progress.renderSize
146
+ }
147
+ };
122
148
  }
123
- });
149
+ if (Date.now() - startedAt > 10 * 60 * 1000) {
150
+ throw new Error(`Timed out waiting for cloud render ${render.renderId} output.`);
151
+ }
152
+ await new Promise((resolve) => setTimeout(resolve, 5000));
153
+ }
124
154
  }
125
155
  async renderLocal(input) {
126
156
  const renderId = createId("render");
@@ -191,18 +221,6 @@ export class RemotionService {
191
221
  }
192
222
  };
193
223
  }
194
- async assertCompositionFps(input) {
195
- const composition = await this.resolveComposition({
196
- source: input.serveUrl,
197
- compositionId: input.compositionId,
198
- inputProps: input.inputProps
199
- });
200
- this.assertExpectedFps({
201
- compositionId: input.compositionId,
202
- actualFps: composition.fps,
203
- expectedFps: input.expectedFps
204
- });
205
- }
206
224
  async resolveComposition(input) {
207
225
  const compositions = await getCompositions(input.source, {
208
226
  inputProps: input.inputProps
@@ -232,43 +250,427 @@ function getOrCreateBundle(entryPoint) {
232
250
  bundleCache.set(entryPoint, created);
233
251
  return created;
234
252
  }
235
- async function withRemotionCredentials(work) {
236
- const previousAccessKeyId = process.env.AWS_ACCESS_KEY_ID;
237
- const previousSecretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
238
- const previousRegion = process.env.AWS_REGION;
239
- if (config.REMOTION_AWS_ACCESS_KEY_ID) {
240
- process.env.AWS_ACCESS_KEY_ID = config.REMOTION_AWS_ACCESS_KEY_ID;
253
+ function roundUsd(value) {
254
+ return Number(value.toFixed(6));
255
+ }
256
+ function collectPreflightUrls(value, path = "inputProps") {
257
+ if (typeof value === "string") {
258
+ return isRemoteHttpUrl(value) ? [{ url: value, label: path }] : [];
241
259
  }
242
- if (config.REMOTION_AWS_SECRET_ACCESS_KEY) {
243
- process.env.AWS_SECRET_ACCESS_KEY = config.REMOTION_AWS_SECRET_ACCESS_KEY;
260
+ if (Array.isArray(value)) {
261
+ return value.flatMap((item, index) => collectPreflightUrls(item, `${path}[${index}]`));
244
262
  }
245
- process.env.AWS_REGION = config.REMOTION_REGION;
246
- try {
247
- return await work();
263
+ if (!value || typeof value !== "object") {
264
+ return [];
248
265
  }
249
- finally {
250
- if (previousAccessKeyId === undefined) {
251
- delete process.env.AWS_ACCESS_KEY_ID;
266
+ return Object.entries(value).flatMap(([key, item]) => (collectPreflightUrls(item, `${path}.${key}`)));
267
+ }
268
+ async function preflightRenderAssets(inputs, options = {}) {
269
+ const normalized = inputs.map((input, index) => (typeof input === "string" ? { url: input, label: `preflightUrls[${index}]` } : input));
270
+ if (options.requireRemoteHttpUrls) {
271
+ const invalid = normalized
272
+ .filter((input) => input.url.trim() && !isRemoteHttpUrl(input.url))
273
+ .map((input) => `${input.label || "asset"} must be an absolute http(s) URL: ${input.url}`);
274
+ if (invalid.length) {
275
+ throw new Error(`Render asset preflight failed: ${invalid.join("; ")}`);
252
276
  }
253
- else {
254
- process.env.AWS_ACCESS_KEY_ID = previousAccessKeyId;
277
+ }
278
+ const targets = dedupePreflightUrls(normalized.filter((input) => isRemoteHttpUrl(input.url)));
279
+ if (!targets.length) {
280
+ return [];
281
+ }
282
+ const failures = [];
283
+ const assets = [];
284
+ for (const target of targets) {
285
+ const result = await preflightOneRenderAsset(target);
286
+ assets.push(result.asset);
287
+ const failure = result.failure;
288
+ if (failure) {
289
+ failures.push(failure.message);
255
290
  }
256
- if (previousSecretAccessKey === undefined) {
257
- delete process.env.AWS_SECRET_ACCESS_KEY;
291
+ }
292
+ if (failures.length) {
293
+ const shown = failures.slice(0, RENDER_ASSET_PREFLIGHT_MAX_FAILURES);
294
+ const remaining = failures.length - shown.length;
295
+ throw new Error([
296
+ "Render asset preflight failed:",
297
+ ...shown.map((failure) => `- ${failure}`),
298
+ remaining > 0 ? `- ${remaining} more asset${remaining === 1 ? "" : "s"} failed preflight.` : null
299
+ ].filter(Boolean).join("\n"));
300
+ }
301
+ return assets;
302
+ }
303
+ function dedupePreflightUrls(inputs) {
304
+ const seen = new Set();
305
+ const deduped = [];
306
+ for (const input of inputs) {
307
+ const key = `${input.label || ""}\n${input.url}`;
308
+ if (seen.has(key)) {
309
+ continue;
258
310
  }
259
- else {
260
- process.env.AWS_SECRET_ACCESS_KEY = previousSecretAccessKey;
311
+ seen.add(key);
312
+ deduped.push(input);
313
+ }
314
+ return deduped;
315
+ }
316
+ async function preflightOneRenderAsset(input) {
317
+ const maxAttempts = isVidfarmTemplateMediaUrl(input.url) ? 4 : 1;
318
+ let latest = null;
319
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
320
+ latest = await preflightOneRenderAssetOnce(input);
321
+ if (!latest.failure || !isRetryablePreflightFailure(latest.failure.status, input.url)) {
322
+ return latest;
261
323
  }
262
- if (previousRegion === undefined) {
263
- delete process.env.AWS_REGION;
324
+ if (attempt < maxAttempts - 1) {
325
+ await sleep(preflightRetryDelayMs(attempt));
264
326
  }
265
- else {
266
- process.env.AWS_REGION = previousRegion;
327
+ }
328
+ return latest ?? preflightOneRenderAssetOnce(input);
329
+ }
330
+ async function preflightOneRenderAssetOnce(input) {
331
+ const headFailure = await requestRenderAsset(input, "HEAD");
332
+ if (!headFailure.failure) {
333
+ return headFailure;
334
+ }
335
+ if (!headFailure.failure.allowGetFallback) {
336
+ return headFailure;
337
+ }
338
+ const getFailure = await requestRenderAsset(input, "GET");
339
+ return {
340
+ asset: mergeAssetMetadata(headFailure.asset, getFailure.asset),
341
+ failure: getFailure.failure
342
+ };
343
+ }
344
+ async function requestRenderAsset(input, method) {
345
+ const controller = new AbortController();
346
+ const timeout = setTimeout(() => controller.abort(), RENDER_ASSET_PREFLIGHT_TIMEOUT_MS);
347
+ try {
348
+ const response = await fetch(input.url, {
349
+ method,
350
+ signal: controller.signal,
351
+ headers: method === "GET" ? { range: "bytes=0-0" } : undefined
352
+ });
353
+ const asset = extractPreflightAssetMetadata(input, response);
354
+ if (response.ok) {
355
+ return { asset, failure: null };
267
356
  }
357
+ return {
358
+ asset,
359
+ failure: {
360
+ allowGetFallback: method === "HEAD",
361
+ status: response.status,
362
+ message: `${input.label || "asset"} returned ${response.status} ${response.statusText || "HTTP error"} for ${formatUrlForError(input.url)}`
363
+ }
364
+ };
365
+ }
366
+ catch (error) {
367
+ const message = error instanceof Error && error.name === "AbortError"
368
+ ? `timed out after ${RENDER_ASSET_PREFLIGHT_TIMEOUT_MS / 1000}s`
369
+ : error instanceof Error
370
+ ? error.message
371
+ : "unknown fetch error";
372
+ return {
373
+ asset: {
374
+ url: input.url,
375
+ label: input.label,
376
+ contentType: null,
377
+ contentLengthBytes: null
378
+ },
379
+ failure: {
380
+ allowGetFallback: false,
381
+ status: null,
382
+ message: `${input.label || "asset"} could not be reached: ${message} (${formatUrlForError(input.url)})`
383
+ }
384
+ };
385
+ }
386
+ finally {
387
+ clearTimeout(timeout);
268
388
  }
269
389
  }
270
- function roundUsd(value) {
271
- return Number(value.toFixed(6));
390
+ function isRetryablePreflightFailure(status, url) {
391
+ return isVidfarmTemplateMediaUrl(url) && (status === 403 || status === 404 || status === 429 || status == null || status >= 500);
392
+ }
393
+ function preflightRetryDelayMs(attempt) {
394
+ return Math.min(250 * (2 ** attempt), 2_000);
395
+ }
396
+ function sleep(ms) {
397
+ return new Promise((resolve) => setTimeout(resolve, ms));
398
+ }
399
+ function isVidfarmTemplateMediaUrl(value) {
400
+ try {
401
+ const url = new URL(value);
402
+ return url.pathname === "/template-media" && Boolean(url.searchParams.get("key"));
403
+ }
404
+ catch {
405
+ return false;
406
+ }
407
+ }
408
+ function isRemoteHttpUrl(value) {
409
+ try {
410
+ const url = new URL(value);
411
+ return url.protocol === "http:" || url.protocol === "https:";
412
+ }
413
+ catch {
414
+ return false;
415
+ }
416
+ }
417
+ function formatUrlForError(url) {
418
+ return url.length > 300 ? `${url.slice(0, 300)}...` : url;
419
+ }
420
+ function extractPreflightAssetMetadata(input, response) {
421
+ return {
422
+ url: input.url,
423
+ label: input.label,
424
+ contentType: normalizeHeaderValue(response.headers.get("content-type")),
425
+ contentLengthBytes: parseContentLength(response.headers)
426
+ };
427
+ }
428
+ function mergeAssetMetadata(primary, fallback) {
429
+ return {
430
+ ...primary,
431
+ contentType: primary.contentType ?? fallback.contentType,
432
+ contentLengthBytes: primary.contentLengthBytes ?? fallback.contentLengthBytes
433
+ };
434
+ }
435
+ function normalizeHeaderValue(value) {
436
+ const trimmed = value?.trim();
437
+ return trimmed ? trimmed : null;
438
+ }
439
+ function parseContentLength(headers) {
440
+ const contentRange = headers.get("content-range");
441
+ if (contentRange) {
442
+ const match = /\/(\d+)\s*$/u.exec(contentRange);
443
+ if (match) {
444
+ return Number.parseInt(match[1], 10);
445
+ }
446
+ }
447
+ const contentLength = headers.get("content-length");
448
+ if (!contentLength) {
449
+ return null;
450
+ }
451
+ const parsed = Number.parseInt(contentLength, 10);
452
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
453
+ }
454
+ function planLambdaRenderPolicy(input) {
455
+ const durationSeconds = input.durationInFrames / input.fps;
456
+ const outputDimensions = inferOutputDimensions(input.inputProps);
457
+ const outputPixels = outputDimensions.width != null && outputDimensions.height != null
458
+ ? outputDimensions.width * outputDimensions.height
459
+ : null;
460
+ const remoteVideoAssets = collectRemoteVideoAssets(input.inputProps, input.preflightAssets);
461
+ const sourceVideo = remoteVideoAssets.primary;
462
+ const totalRemoteVideoBytes = remoteVideoAssets.totalBytes;
463
+ if (durationSeconds > ABSOLUTE_MAX_REMOTE_VIDEO_DURATION_SECONDS) {
464
+ throw new Error(`Render duration is ${formatDurationSeconds(durationSeconds)}, which exceeds the current Vidfarm remote-video platform limit of ${formatDurationSeconds(ABSOLUTE_MAX_REMOTE_VIDEO_DURATION_SECONDS)}. Trim the source video before rendering.`);
465
+ }
466
+ const sourceVideoBytes = sourceVideo?.contentLengthBytes ?? null;
467
+ if (sourceVideoBytes != null && sourceVideoBytes > ABSOLUTE_MAX_DIRECT_REMOTE_VIDEO_BYTES) {
468
+ throw new Error(`Remote video source is ${formatBytes(sourceVideoBytes)}, which exceeds the direct Vidfarm render limit of ${formatBytes(ABSOLUTE_MAX_DIRECT_REMOTE_VIDEO_BYTES)}. Run POST /api/v1/primitives/videos/normalize first, then pass the returned normalizedVideoUrl into the template render.`);
469
+ }
470
+ const maxSafeSegmentDurationSeconds = inferMaxSafeSegmentDurationSeconds({
471
+ sourceVideoBytes,
472
+ durationSeconds,
473
+ outputPixels
474
+ });
475
+ const requiredLambdasByDuration = Math.max(1, Math.ceil(durationSeconds / maxSafeSegmentDurationSeconds));
476
+ const maxLambdasByDownloadBudget = inferMaxLambdasByDownloadBudget(totalRemoteVideoBytes);
477
+ const preferredLambdasByRequestedFrames = Math.max(1, Math.ceil(input.durationInFrames / Math.max(1, input.requestedFramesPerLambda)));
478
+ if (requiredLambdasByDuration > maxLambdasByDownloadBudget) {
479
+ const sourceDescriptor = sourceVideoBytes == null
480
+ ? `${formatDurationSeconds(durationSeconds)} remote-video render`
481
+ : `${formatBytes(sourceVideoBytes)} over ${formatDurationSeconds(durationSeconds)}`;
482
+ throw new Error(`Remote-video render workload (${sourceDescriptor}) exceeds the current Vidfarm Lambda render envelope. The platform would need about ${requiredLambdasByDuration} chunks to keep per-chunk runtime safe, but remote media download policy caps this workload at ${maxLambdasByDownloadBudget} chunk${maxLambdasByDownloadBudget === 1 ? "" : "s"}. Trim, shorten, or pre-compress the source video before rendering.`);
483
+ }
484
+ const parallelLambdaTarget = inferParallelLambdaTarget({
485
+ sourceVideoBytes,
486
+ totalRemoteVideoBytes,
487
+ outputPixels,
488
+ preferredLambdasByRequestedFrames
489
+ });
490
+ const targetLambdas = Math.max(requiredLambdasByDuration, Math.min(preferredLambdasByRequestedFrames, maxLambdasByDownloadBudget, parallelLambdaTarget));
491
+ const strategy = targetLambdas <= 1 ? "single" : targetLambdas <= 3 ? "few" : "parallel";
492
+ const reason = describeLambdaPolicyReason({
493
+ targetLambdas,
494
+ preferredLambdasByRequestedFrames,
495
+ maxSafeSegmentDurationSeconds,
496
+ sourceVideoBytes,
497
+ totalRemoteVideoBytes,
498
+ durationSeconds
499
+ });
500
+ const adaptiveFramesPerLambda = Math.max(input.requestedFramesPerLambda, Math.ceil(input.durationInFrames / targetLambdas));
501
+ return {
502
+ framesPerLambda: Math.max(1, adaptiveFramesPerLambda),
503
+ estimatedLambdaCount: Math.max(1, Math.ceil(input.durationInFrames / Math.max(1, adaptiveFramesPerLambda))),
504
+ strategy,
505
+ reason,
506
+ sourceVideoUrl: sourceVideo?.url ?? null,
507
+ sourceVideoBytes,
508
+ totalRemoteVideoBytes,
509
+ remoteVideoCount: remoteVideoAssets.assets.length,
510
+ durationSeconds,
511
+ outputWidth: outputDimensions.width,
512
+ outputHeight: outputDimensions.height,
513
+ outputPixels
514
+ };
515
+ }
516
+ function collectRemoteVideoAssets(inputProps, assets) {
517
+ const prioritizedLabels = new Set(collectRemoteVideoLabels(inputProps));
518
+ const prioritized = assets.filter((asset) => prioritizedLabels.has(asset.label ?? ""));
519
+ const candidates = prioritized.length ? prioritized : assets;
520
+ const videoAssets = candidates.filter((asset) => isLikelyVideoAsset(asset));
521
+ const totalBytes = videoAssets.reduce((sum, asset) => (sum == null || asset.contentLengthBytes == null ? null : sum + asset.contentLengthBytes), 0);
522
+ return {
523
+ primary: videoAssets[0] ?? null,
524
+ assets: videoAssets,
525
+ totalBytes
526
+ };
527
+ }
528
+ function collectRemoteVideoLabels(value, path = "inputProps") {
529
+ if (typeof value === "string") {
530
+ return isRemoteVideoLabel(path) && isRemoteHttpUrl(value) ? [path] : [];
531
+ }
532
+ if (Array.isArray(value)) {
533
+ return value.flatMap((item, index) => collectRemoteVideoLabels(item, `${path}[${index}]`));
534
+ }
535
+ if (!value || typeof value !== "object") {
536
+ return [];
537
+ }
538
+ return Object.entries(value).flatMap(([key, item]) => (collectRemoteVideoLabels(item, `${path}.${key}`)));
539
+ }
540
+ function isRemoteVideoLabel(label) {
541
+ return /(source_video_url|backgroundVideoUrl|background_video_url|video_url)$/u.test(label);
542
+ }
543
+ function isLikelyVideoAsset(asset) {
544
+ if (asset.contentType?.toLowerCase().startsWith("video/")) {
545
+ return true;
546
+ }
547
+ return /\.(mp4|mov|m4v|webm)(?:$|[?#])/iu.test(asset.url);
548
+ }
549
+ function inferMaxSafeSegmentDurationSeconds(input) {
550
+ let maxSafeDurationSeconds = inferBaselineMaxSafeSegmentDurationSeconds({
551
+ sourceVideoBytes: input.sourceVideoBytes,
552
+ durationSeconds: input.durationSeconds
553
+ });
554
+ const outputPixels = input.outputPixels;
555
+ if (outputPixels != null) {
556
+ if (outputPixels >= 1920 * 1080) {
557
+ maxSafeDurationSeconds = Math.min(maxSafeDurationSeconds, input.durationSeconds <= 30 ? 24 : 20);
558
+ }
559
+ else if (outputPixels >= 1280 * 720) {
560
+ maxSafeDurationSeconds = Math.min(maxSafeDurationSeconds, input.durationSeconds <= 30 ? 36 : 30);
561
+ }
562
+ else if (outputPixels >= 854 * 480) {
563
+ maxSafeDurationSeconds = Math.min(maxSafeDurationSeconds, 45);
564
+ }
565
+ }
566
+ return maxSafeDurationSeconds;
567
+ }
568
+ function inferBaselineMaxSafeSegmentDurationSeconds(input) {
569
+ const bytes = input.sourceVideoBytes;
570
+ if (bytes == null) {
571
+ return input.durationSeconds <= 30 ? 45 : 60;
572
+ }
573
+ if (bytes >= 400 * 1024 * 1024) {
574
+ return 30;
575
+ }
576
+ if (bytes >= 250 * 1024 * 1024) {
577
+ return input.durationSeconds <= 30 ? 30 : 25;
578
+ }
579
+ if (bytes >= 200 * 1024 * 1024) {
580
+ return input.durationSeconds <= 30 ? 35 : 25;
581
+ }
582
+ if (bytes >= 150 * 1024 * 1024) {
583
+ return input.durationSeconds <= 45 ? 45 : 30;
584
+ }
585
+ if (bytes >= 75 * 1024 * 1024) {
586
+ return 90;
587
+ }
588
+ return 120;
589
+ }
590
+ function inferOutputDimensions(inputProps) {
591
+ return {
592
+ width: parsePositiveInteger(inputProps.output_width),
593
+ height: parsePositiveInteger(inputProps.output_height)
594
+ };
595
+ }
596
+ function parsePositiveInteger(value) {
597
+ if (typeof value !== "number" || !Number.isFinite(value)) {
598
+ return null;
599
+ }
600
+ const normalized = Math.floor(value);
601
+ return normalized > 0 ? normalized : null;
602
+ }
603
+ function inferMaxLambdasByDownloadBudget(totalRemoteVideoBytes) {
604
+ if (totalRemoteVideoBytes == null || totalRemoteVideoBytes <= 0) {
605
+ return DEFAULT_PARALLEL_LAMBDA_TARGET;
606
+ }
607
+ return Math.max(1, Math.min(MAX_PARALLEL_LAMBDA_TARGET, Math.floor(REMOTE_VIDEO_DOWNLOAD_BUDGET_BYTES / totalRemoteVideoBytes)));
608
+ }
609
+ function inferParallelLambdaTarget(input) {
610
+ const requestedTarget = Math.max(1, input.preferredLambdasByRequestedFrames);
611
+ const totalRemoteBytes = input.totalRemoteVideoBytes ?? input.sourceVideoBytes;
612
+ const outputPixels = input.outputPixels ?? 0;
613
+ if (totalRemoteBytes != null && totalRemoteBytes <= SMALL_REMOTE_VIDEO_BYTES) {
614
+ return Math.min(MAX_PARALLEL_LAMBDA_TARGET, Math.max(DEFAULT_PARALLEL_LAMBDA_TARGET, requestedTarget));
615
+ }
616
+ if (totalRemoteBytes != null
617
+ && totalRemoteBytes <= MEDIUM_REMOTE_VIDEO_BYTES
618
+ && outputPixels <= 1080 * 1920) {
619
+ return Math.min(18, Math.max(DEFAULT_PARALLEL_LAMBDA_TARGET, requestedTarget));
620
+ }
621
+ if (totalRemoteBytes != null
622
+ && totalRemoteBytes <= ABSOLUTE_MAX_DIRECT_REMOTE_VIDEO_BYTES
623
+ && outputPixels <= 1080 * 1920) {
624
+ return Math.min(12, Math.max(DEFAULT_PARALLEL_LAMBDA_TARGET, requestedTarget));
625
+ }
626
+ return DEFAULT_PARALLEL_LAMBDA_TARGET;
627
+ }
628
+ function describeLambdaPolicyReason(input) {
629
+ const sourcePart = input.sourceVideoBytes == null ? "unknown source size" : `${formatBytes(input.sourceVideoBytes)} source`;
630
+ const totalPart = input.totalRemoteVideoBytes == null ? "unknown total remote bytes" : `${formatBytes(input.totalRemoteVideoBytes)} total remote video`;
631
+ return `${sourcePart}, ${formatDurationSeconds(input.durationSeconds)} duration, requested ~${input.preferredLambdasByRequestedFrames} chunk${input.preferredLambdasByRequestedFrames === 1 ? "" : "s"}, target <=${formatDurationSeconds(input.maxSafeSegmentDurationSeconds)} per chunk, ${totalPart}`;
632
+ }
633
+ function formatBytes(bytes) {
634
+ if (bytes >= 1024 * 1024) {
635
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
636
+ }
637
+ if (bytes >= 1024) {
638
+ return `${(bytes / 1024).toFixed(1)}KB`;
639
+ }
640
+ return `${bytes}B`;
641
+ }
642
+ function formatDurationSeconds(seconds) {
643
+ return `${seconds.toFixed(seconds >= 10 ? 0 : 1)}s`;
644
+ }
645
+ function summarizeFatalRenderError(progress) {
646
+ if (!progress || typeof progress !== "object") {
647
+ return null;
648
+ }
649
+ const maybeProgress = progress;
650
+ if (maybeProgress.fatalErrorEncountered !== true) {
651
+ return null;
652
+ }
653
+ const errors = Array.isArray(maybeProgress.errors) ? maybeProgress.errors : [];
654
+ const messages = errors
655
+ .map((error) => summarizeRenderError(error))
656
+ .filter(Boolean)
657
+ .slice(0, 3);
658
+ return messages.length ? messages.join(" | ") : "Remotion reported a fatal render error.";
659
+ }
660
+ function summarizeRenderError(error) {
661
+ if (!error || typeof error !== "object") {
662
+ return typeof error === "string" ? error : null;
663
+ }
664
+ const record = error;
665
+ const message = firstString(record.message, record.name, record.stack, record.explanation)
666
+ ?? firstString(record.error?.message);
667
+ const chunk = typeof record.chunk === "number" || typeof record.chunk === "string"
668
+ ? `chunk ${record.chunk}: `
669
+ : "";
670
+ return message ? `${chunk}${message}` : JSON.stringify(record).slice(0, 500);
671
+ }
672
+ function firstString(...values) {
673
+ return values.find((value) => typeof value === "string" && value.trim().length > 0)?.trim() ?? null;
272
674
  }
273
675
  function nonBlank(value) {
274
676
  const trimmed = value?.trim();
@@ -283,3 +685,4 @@ function inferHostedServeUrl(compositionId) {
283
685
  const encodedSiteName = siteName.split("/").map(encodeURIComponent).join("/");
284
686
  return `https://${bucketName}.s3.${config.REMOTION_REGION}.amazonaws.com/sites/${encodedSiteName}/index.html`;
285
687
  }
688
+ //# sourceMappingURL=remotion.js.map