@mevdragon/vidfarm-devcli 0.2.12 → 0.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +37 -1
- package/README.md +27 -183
- package/SKILL.director.md +293 -0
- package/SKILL.platform.md +312 -0
- package/auto-create-hyperframe-templates/AUTO_CREATE_HYPERFRAME_TEMPLATES.md +275 -0
- package/auto-create-hyperframe-templates/extractor-system-prompt.md +128 -0
- package/auto-create-hyperframe-templates/input-manifest.schema.json +56 -0
- package/auto-create-hyperframe-templates/lambda-memory-estimator.md +118 -0
- package/auto-create-hyperframe-templates/production-graph.schema.json +314 -0
- package/auto-create-hyperframe-templates/runbook.md +198 -0
- package/auto-create-hyperframe-templates/scripts/create-hyperframe-template.mjs +368 -0
- package/auto-create-hyperframe-templates/template-plan.schema.json +311 -0
- package/auto-create-hyperframe-templates/template-planner-prompt.md +144 -0
- package/auto-create-templates/AUTO_CREATE_TEMPLATES.md +116 -0
- package/auto-create-templates/extractor-system-prompt.md +6 -1
- package/auto-create-templates/template-plan.schema.json +73 -2
- package/auto-create-templates/template-planner-prompt.md +62 -1
- package/dist/src/account-pages-legacy.js +363 -89
- package/dist/src/account-pages-legacy.js.map +1 -0
- package/dist/src/account-pages.js +3 -2
- package/dist/src/account-pages.js.map +1 -0
- package/dist/src/app.js +7096 -1294
- package/dist/src/app.js.map +1 -0
- package/dist/src/cli.js +115 -29
- package/dist/src/cli.js.map +1 -0
- package/dist/src/composition-runtime.js +581 -0
- package/dist/src/composition-runtime.js.map +1 -0
- package/dist/src/config.js +69 -11
- package/dist/src/config.js.map +1 -0
- package/dist/src/context.js +280 -21
- package/dist/src/context.js.map +1 -0
- package/dist/src/dev-app-legacy.js +18 -17
- package/dist/src/dev-app-legacy.js.map +1 -0
- package/dist/src/dev-app.js +1 -0
- package/dist/src/dev-app.js.map +1 -0
- package/dist/src/dev-serve.js +169 -0
- package/dist/src/dev-serve.js.map +1 -0
- package/dist/src/domain.js +2 -1
- package/dist/src/domain.js.map +1 -0
- package/dist/src/editor-chat-history.js +10 -0
- package/dist/src/editor-chat-history.js.map +1 -0
- package/dist/src/editor-chat.js +135 -25
- package/dist/src/editor-chat.js.map +1 -0
- package/dist/src/editor-dark-theme.js +1128 -0
- package/dist/src/editor-dark-theme.js.map +1 -0
- package/dist/src/frontend/flockposter-cache-store.js +10 -2
- package/dist/src/frontend/flockposter-cache-store.js.map +1 -0
- package/dist/src/frontend/homepage-client.js +6 -3
- package/dist/src/frontend/homepage-client.js.map +1 -0
- package/dist/src/frontend/homepage-shared.js +1 -0
- package/dist/src/frontend/homepage-shared.js.map +1 -0
- package/dist/src/frontend/homepage-store.js +1 -0
- package/dist/src/frontend/homepage-store.js.map +1 -0
- package/dist/src/frontend/homepage-view.js +131 -18
- package/dist/src/frontend/homepage-view.js.map +1 -0
- package/dist/src/frontend/page-runtime-client.js +27 -3
- package/dist/src/frontend/page-runtime-client.js.map +1 -0
- package/dist/src/frontend/page-runtime-store.js +1 -0
- package/dist/src/frontend/page-runtime-store.js.map +1 -0
- package/dist/src/frontend/sentry.js +42 -0
- package/dist/src/frontend/sentry.js.map +1 -0
- package/dist/src/frontend/template-editor-chat.js +1711 -212
- package/dist/src/frontend/template-editor-chat.js.map +1 -0
- package/dist/src/help-page.js +333 -0
- package/dist/src/help-page.js.map +1 -0
- package/dist/src/homepage.js +169 -47
- package/dist/src/homepage.js.map +1 -0
- package/dist/src/hyperframes/composition.js +180 -0
- package/dist/src/hyperframes/composition.js.map +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/instrument.js +30 -0
- package/dist/src/instrument.js.map +1 -0
- package/dist/src/lib/crypto.js +1 -0
- package/dist/src/lib/crypto.js.map +1 -0
- package/dist/src/lib/dev-log.js +54 -0
- package/dist/src/lib/dev-log.js.map +1 -0
- package/dist/src/lib/display-name.js +11 -0
- package/dist/src/lib/display-name.js.map +1 -0
- package/dist/src/lib/ids.js +21 -1
- package/dist/src/lib/ids.js.map +1 -0
- package/dist/src/lib/images.js +1 -0
- package/dist/src/lib/images.js.map +1 -0
- package/dist/src/lib/json.js +1 -0
- package/dist/src/lib/json.js.map +1 -0
- package/dist/src/lib/template-dna.js +1 -0
- package/dist/src/lib/template-dna.js.map +1 -0
- package/dist/src/lib/template-paths.js +1 -0
- package/dist/src/lib/template-paths.js.map +1 -0
- package/dist/src/lib/template-style-options.js +29 -3
- package/dist/src/lib/template-style-options.js.map +1 -0
- package/dist/src/lib/time.js +1 -0
- package/dist/src/lib/time.js.map +1 -0
- package/dist/src/lib/video-quality-harness.js +60 -0
- package/dist/src/lib/video-quality-harness.js.map +1 -0
- package/dist/src/page-runtime.js +1 -0
- package/dist/src/page-runtime.js.map +1 -0
- package/dist/src/page-shell.js +403 -29
- package/dist/src/page-shell.js.map +1 -0
- package/dist/src/primitive-context.js +144 -8
- package/dist/src/primitive-context.js.map +1 -0
- package/dist/src/primitive-registry.js +1919 -78
- package/dist/src/primitive-registry.js.map +1 -0
- package/dist/src/primitive-sdk.js +1 -0
- package/dist/src/primitive-sdk.js.map +1 -0
- package/dist/src/primitives/remotion/html-image.js +3 -1
- package/dist/src/primitives/remotion/html-image.js.map +1 -0
- package/dist/src/primitives/remotion/media-slideshow.js +60 -0
- package/dist/src/primitives/remotion/media-slideshow.js.map +1 -0
- package/dist/src/react-page-shell.js +1 -0
- package/dist/src/react-page-shell.js.map +1 -0
- package/dist/src/ready-post-schedule-component.js +1 -0
- package/dist/src/ready-post-schedule-component.js.map +1 -0
- package/dist/src/registry.js +237 -24
- package/dist/src/registry.js.map +1 -0
- package/dist/src/runtime.js +3 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/services/api-call-history.js +4 -0
- package/dist/src/services/api-call-history.js.map +1 -0
- package/dist/src/services/auth.js +40 -50
- package/dist/src/services/auth.js.map +1 -0
- package/dist/src/services/billing.js +20 -44
- package/dist/src/services/billing.js.map +1 -0
- package/dist/src/services/chat-threads.js +10 -6
- package/dist/src/services/chat-threads.js.map +1 -0
- package/dist/src/services/fork-access.js +93 -0
- package/dist/src/services/fork-access.js.map +1 -0
- package/dist/src/services/fork-manifest.js +43 -0
- package/dist/src/services/fork-manifest.js.map +1 -0
- package/dist/src/services/ghostcut.js +132 -0
- package/dist/src/services/ghostcut.js.map +1 -0
- package/dist/src/services/hyperframes.js +1014 -0
- package/dist/src/services/hyperframes.js.map +1 -0
- package/dist/src/services/job-capacity.js +14 -0
- package/dist/src/services/job-capacity.js.map +1 -0
- package/dist/src/services/job-logs.js +4 -0
- package/dist/src/services/job-logs.js.map +1 -0
- package/dist/src/services/jobs.js +99 -91
- package/dist/src/services/jobs.js.map +1 -0
- package/dist/src/services/media-processing.js +743 -0
- package/dist/src/services/media-processing.js.map +1 -0
- package/dist/src/services/primitive-media-lambda.js +280 -0
- package/dist/src/services/primitive-media-lambda.js.map +1 -0
- package/dist/src/services/providers.js +1560 -178
- package/dist/src/services/providers.js.map +1 -0
- package/dist/src/services/rate-limits.js +3 -2
- package/dist/src/services/rate-limits.js.map +1 -0
- package/dist/src/services/remotion.js +495 -92
- package/dist/src/services/remotion.js.map +1 -0
- package/dist/src/services/serverless-auth.js +374 -0
- package/dist/src/services/serverless-auth.js.map +1 -0
- package/dist/src/services/serverless-jobs.js +1074 -0
- package/dist/src/services/serverless-jobs.js.map +1 -0
- package/dist/src/services/serverless-provider-keys.js +401 -0
- package/dist/src/services/serverless-provider-keys.js.map +1 -0
- package/dist/src/services/serverless-records.js +1088 -0
- package/dist/src/services/serverless-records.js.map +1 -0
- package/dist/src/services/serverless-template-configs.js +67 -0
- package/dist/src/services/serverless-template-configs.js.map +1 -0
- package/dist/src/services/storage.js +171 -35
- package/dist/src/services/storage.js.map +1 -0
- package/dist/src/services/template-certification.js +295 -3
- package/dist/src/services/template-certification.js.map +1 -0
- package/dist/src/services/template-loader.js +45 -1
- package/dist/src/services/template-loader.js.map +1 -0
- package/dist/src/services/template-runtime-bundles.js +217 -0
- package/dist/src/services/template-runtime-bundles.js.map +1 -0
- package/dist/src/services/template-sources.js +452 -87
- package/dist/src/services/template-sources.js.map +1 -0
- package/dist/src/services/video-normalization.js +2 -0
- package/dist/src/services/video-normalization.js.map +1 -0
- package/dist/src/services/webhooks.js +7 -6
- package/dist/src/services/webhooks.js.map +1 -0
- package/dist/src/template-editor-pages.js +2051 -1869
- package/dist/src/template-editor-pages.js.map +1 -0
- package/dist/src/template-editor-shell.js +1376 -181
- package/dist/src/template-editor-shell.js.map +1 -0
- package/dist/src/template-sdk.js +1 -0
- package/dist/src/template-sdk.js.map +1 -0
- package/dist/src/worker.js +11 -226
- package/dist/src/worker.js.map +1 -0
- package/package.json +47 -12
- package/public/assets/homepage-client-app.js +34 -8
- package/public/assets/page-runtime-client-app.js +44 -18
- package/readme.secret.md +89 -0
- package/templates/vidfarm_template_0000/README.md +106 -0
- package/templates/vidfarm_template_0000/SKILL.md +266 -0
- package/templates/vidfarm_template_0000/assets/Abel-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/DMSerifDisplay-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/Montserrat[wght].ttf +0 -0
- package/templates/vidfarm_template_0000/assets/SourceCodePro[wght].ttf +0 -0
- package/templates/vidfarm_template_0000/assets/TikTokSans-SemiBold.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/Yesteryear-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/composition.json +11 -0
- package/templates/vidfarm_template_0000/src/lib/images.js +221 -0
- package/templates/vidfarm_template_0000/src/remotion/Root.js +33 -0
- package/templates/vidfarm_template_0000/src/remotion/index.js +3 -0
- package/templates/vidfarm_template_0000/src/sdk.js +3 -0
- package/templates/vidfarm_template_0000/src/style-options.js +200 -0
- package/templates/vidfarm_template_0000/src/template-dna.js +9 -0
- package/templates/vidfarm_template_0000/src/template.js +1566 -0
- package/templates/vidfarm_template_0000/template.config.json +21 -0
- package/GETTING_STARTED.developers.md +0 -87
- package/SKILL.developer.md +0 -1186
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (
|
|
240
|
-
|
|
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 (
|
|
243
|
-
|
|
260
|
+
if (Array.isArray(value)) {
|
|
261
|
+
return value.flatMap((item, index) => collectPreflightUrls(item, `${path}[${index}]`));
|
|
244
262
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return await work();
|
|
263
|
+
if (!value || typeof value !== "object") {
|
|
264
|
+
return [];
|
|
248
265
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
254
|
-
|
|
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
|
-
|
|
257
|
-
|
|
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
|
-
|
|
260
|
-
|
|
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 (
|
|
263
|
-
|
|
324
|
+
if (attempt < maxAttempts - 1) {
|
|
325
|
+
await sleep(preflightRetryDelayMs(attempt));
|
|
264
326
|
}
|
|
265
|
-
|
|
266
|
-
|
|
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
|
|
271
|
-
return
|
|
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
|