@mevdragon/vidfarm-devcli 0.2.14 → 0.3.1
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/README.md +11 -4
- package/SKILL.director.md +100 -0
- package/SKILL.platform.md +66 -6
- package/dist/src/cli.js +135 -1527
- package/package.json +5 -24
- package/.env.example +0 -60
- package/auto-create-hyperframe-templates/AUTO_CREATE_HYPERFRAME_TEMPLATES.md +0 -275
- package/auto-create-hyperframe-templates/extractor-system-prompt.md +0 -128
- package/auto-create-hyperframe-templates/input-manifest.schema.json +0 -56
- package/auto-create-hyperframe-templates/lambda-memory-estimator.md +0 -118
- package/auto-create-hyperframe-templates/production-graph.schema.json +0 -314
- package/auto-create-hyperframe-templates/runbook.md +0 -198
- package/auto-create-hyperframe-templates/scripts/create-hyperframe-template.mjs +0 -368
- package/auto-create-hyperframe-templates/template-plan.schema.json +0 -311
- package/auto-create-hyperframe-templates/template-planner-prompt.md +0 -144
- package/auto-create-templates/AUTO_CREATE_TEMPLATES.md +0 -599
- package/auto-create-templates/extractor-system-prompt.md +0 -119
- package/auto-create-templates/production-graph.schema.json +0 -276
- package/auto-create-templates/runbook.md +0 -140
- package/auto-create-templates/template-plan.schema.json +0 -301
- package/auto-create-templates/template-planner-prompt.md +0 -137
- package/dist/src/account-pages-legacy.js +0 -9338
- package/dist/src/account-pages-legacy.js.map +0 -1
- package/dist/src/account-pages.js +0 -52
- package/dist/src/account-pages.js.map +0 -1
- package/dist/src/app.js +0 -13711
- package/dist/src/app.js.map +0 -1
- package/dist/src/cli.js.map +0 -1
- package/dist/src/composition-runtime.js +0 -581
- package/dist/src/composition-runtime.js.map +0 -1
- package/dist/src/config.js +0 -140
- package/dist/src/config.js.map +0 -1
- package/dist/src/context.js +0 -492
- package/dist/src/context.js.map +0 -1
- package/dist/src/dev-app-legacy.js +0 -694
- package/dist/src/dev-app-legacy.js.map +0 -1
- package/dist/src/dev-app.js +0 -6
- package/dist/src/dev-app.js.map +0 -1
- package/dist/src/dev-serve.js.map +0 -1
- package/dist/src/domain.js +0 -2
- package/dist/src/domain.js.map +0 -1
- package/dist/src/editor-chat-history.js +0 -82
- package/dist/src/editor-chat-history.js.map +0 -1
- package/dist/src/editor-chat.js +0 -312
- package/dist/src/editor-chat.js.map +0 -1
- package/dist/src/editor-dark-theme.js +0 -1128
- package/dist/src/editor-dark-theme.js.map +0 -1
- package/dist/src/frontend/flockposter-cache-store.js +0 -124
- package/dist/src/frontend/flockposter-cache-store.js.map +0 -1
- package/dist/src/frontend/homepage-client.js +0 -117
- package/dist/src/frontend/homepage-client.js.map +0 -1
- package/dist/src/frontend/homepage-shared.js +0 -4
- package/dist/src/frontend/homepage-shared.js.map +0 -1
- package/dist/src/frontend/homepage-store.js +0 -28
- package/dist/src/frontend/homepage-store.js.map +0 -1
- package/dist/src/frontend/homepage-view.js +0 -260
- package/dist/src/frontend/homepage-view.js.map +0 -1
- package/dist/src/frontend/page-runtime-client.js +0 -124
- package/dist/src/frontend/page-runtime-client.js.map +0 -1
- package/dist/src/frontend/page-runtime-store.js +0 -9
- package/dist/src/frontend/page-runtime-store.js.map +0 -1
- package/dist/src/frontend/sentry.js +0 -42
- package/dist/src/frontend/sentry.js.map +0 -1
- package/dist/src/frontend/template-editor-chat.js +0 -3760
- package/dist/src/frontend/template-editor-chat.js.map +0 -1
- package/dist/src/help-page.js +0 -333
- package/dist/src/help-page.js.map +0 -1
- package/dist/src/homepage.js +0 -919
- package/dist/src/homepage.js.map +0 -1
- package/dist/src/hyperframes/composition.js +0 -180
- package/dist/src/hyperframes/composition.js.map +0 -1
- package/dist/src/index.js +0 -16
- package/dist/src/index.js.map +0 -1
- package/dist/src/instrument.js +0 -30
- package/dist/src/instrument.js.map +0 -1
- package/dist/src/lib/crypto.js +0 -45
- package/dist/src/lib/crypto.js.map +0 -1
- package/dist/src/lib/dev-log.js +0 -54
- package/dist/src/lib/dev-log.js.map +0 -1
- package/dist/src/lib/display-name.js +0 -11
- package/dist/src/lib/display-name.js.map +0 -1
- package/dist/src/lib/ids.js +0 -24
- package/dist/src/lib/ids.js.map +0 -1
- package/dist/src/lib/images.js +0 -19
- package/dist/src/lib/images.js.map +0 -1
- package/dist/src/lib/json.js +0 -15
- package/dist/src/lib/json.js.map +0 -1
- package/dist/src/lib/template-dna.js +0 -543
- package/dist/src/lib/template-dna.js.map +0 -1
- package/dist/src/lib/template-paths.js +0 -28
- package/dist/src/lib/template-paths.js.map +0 -1
- package/dist/src/lib/template-style-options.js +0 -203
- package/dist/src/lib/template-style-options.js.map +0 -1
- package/dist/src/lib/time.js +0 -7
- package/dist/src/lib/time.js.map +0 -1
- package/dist/src/lib/video-quality-harness.js +0 -60
- package/dist/src/lib/video-quality-harness.js.map +0 -1
- package/dist/src/page-runtime.js +0 -2
- package/dist/src/page-runtime.js.map +0 -1
- package/dist/src/page-shell.js +0 -1315
- package/dist/src/page-shell.js.map +0 -1
- package/dist/src/primitive-context.js +0 -299
- package/dist/src/primitive-context.js.map +0 -1
- package/dist/src/primitive-registry.js +0 -2181
- package/dist/src/primitive-registry.js.map +0 -1
- package/dist/src/primitive-sdk.js +0 -4
- package/dist/src/primitive-sdk.js.map +0 -1
- package/dist/src/primitives/remotion/html-image.js +0 -30
- package/dist/src/primitives/remotion/html-image.js.map +0 -1
- package/dist/src/primitives/remotion/media-slideshow.js +0 -60
- package/dist/src/primitives/remotion/media-slideshow.js.map +0 -1
- package/dist/src/react-page-shell.js +0 -35
- package/dist/src/react-page-shell.js.map +0 -1
- package/dist/src/ready-post-schedule-component.js +0 -1515
- package/dist/src/ready-post-schedule-component.js.map +0 -1
- package/dist/src/registry.js +0 -296
- package/dist/src/registry.js.map +0 -1
- package/dist/src/runtime.js +0 -31
- package/dist/src/runtime.js.map +0 -1
- package/dist/src/services/api-call-history.js +0 -249
- package/dist/src/services/api-call-history.js.map +0 -1
- package/dist/src/services/auth.js +0 -152
- package/dist/src/services/auth.js.map +0 -1
- package/dist/src/services/billing.js +0 -231
- package/dist/src/services/billing.js.map +0 -1
- package/dist/src/services/chat-threads.js +0 -92
- package/dist/src/services/chat-threads.js.map +0 -1
- package/dist/src/services/fork-access.js +0 -93
- package/dist/src/services/fork-access.js.map +0 -1
- package/dist/src/services/fork-manifest.js +0 -43
- package/dist/src/services/fork-manifest.js.map +0 -1
- package/dist/src/services/ghostcut.js +0 -132
- package/dist/src/services/ghostcut.js.map +0 -1
- package/dist/src/services/hyperframes.js +0 -1014
- package/dist/src/services/hyperframes.js.map +0 -1
- package/dist/src/services/job-capacity.js +0 -14
- package/dist/src/services/job-capacity.js.map +0 -1
- package/dist/src/services/job-logs.js +0 -197
- package/dist/src/services/job-logs.js.map +0 -1
- package/dist/src/services/jobs.js +0 -136
- package/dist/src/services/jobs.js.map +0 -1
- package/dist/src/services/media-processing.js +0 -743
- package/dist/src/services/media-processing.js.map +0 -1
- package/dist/src/services/primitive-media-lambda.js +0 -280
- package/dist/src/services/primitive-media-lambda.js.map +0 -1
- package/dist/src/services/providers.js +0 -2870
- package/dist/src/services/providers.js.map +0 -1
- package/dist/src/services/rate-limits.js +0 -237
- package/dist/src/services/rate-limits.js.map +0 -1
- package/dist/src/services/remotion.js +0 -688
- package/dist/src/services/remotion.js.map +0 -1
- package/dist/src/services/serverless-auth.js +0 -374
- package/dist/src/services/serverless-auth.js.map +0 -1
- package/dist/src/services/serverless-jobs.js +0 -1074
- package/dist/src/services/serverless-jobs.js.map +0 -1
- package/dist/src/services/serverless-provider-keys.js +0 -401
- package/dist/src/services/serverless-provider-keys.js.map +0 -1
- package/dist/src/services/serverless-records.js +0 -1088
- package/dist/src/services/serverless-records.js.map +0 -1
- package/dist/src/services/serverless-template-configs.js +0 -67
- package/dist/src/services/serverless-template-configs.js.map +0 -1
- package/dist/src/services/storage.js +0 -383
- package/dist/src/services/storage.js.map +0 -1
- package/dist/src/services/template-certification.js +0 -475
- package/dist/src/services/template-certification.js.map +0 -1
- package/dist/src/services/template-loader.js +0 -99
- package/dist/src/services/template-loader.js.map +0 -1
- package/dist/src/services/template-runtime-bundles.js +0 -217
- package/dist/src/services/template-runtime-bundles.js.map +0 -1
- package/dist/src/services/template-sources.js +0 -1017
- package/dist/src/services/template-sources.js.map +0 -1
- package/dist/src/services/video-normalization.js +0 -2
- package/dist/src/services/video-normalization.js.map +0 -1
- package/dist/src/services/webhooks.js +0 -62
- package/dist/src/services/webhooks.js.map +0 -1
- package/dist/src/template-editor-pages.js +0 -2491
- package/dist/src/template-editor-pages.js.map +0 -1
- package/dist/src/template-editor-shell.js +0 -2702
- package/dist/src/template-editor-shell.js.map +0 -1
- package/dist/src/template-sdk.js +0 -4
- package/dist/src/template-sdk.js.map +0 -1
- package/dist/src/worker.js +0 -17
- package/dist/src/worker.js.map +0 -1
- package/public/assets/homepage-app.js +0 -54
- package/public/assets/homepage-client-app.js +0 -80
- package/public/assets/page-runtime-client-app.js +0 -94
- package/readme.secret.md +0 -89
- package/templates/vidfarm_template_0000/README.md +0 -106
- package/templates/vidfarm_template_0000/SKILL.md +0 -266
- 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 +0 -11
- package/templates/vidfarm_template_0000/src/lib/images.js +0 -221
- package/templates/vidfarm_template_0000/src/remotion/Root.js +0 -33
- package/templates/vidfarm_template_0000/src/remotion/index.js +0 -3
- package/templates/vidfarm_template_0000/src/sdk.js +0 -3
- package/templates/vidfarm_template_0000/src/style-options.js +0 -200
- package/templates/vidfarm_template_0000/src/template-dna.js +0 -9
- package/templates/vidfarm_template_0000/src/template.js +0 -1566
- package/templates/vidfarm_template_0000/template.config.json +0 -21
|
@@ -1,688 +0,0 @@
|
|
|
1
|
-
import { mkdirSync } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { bundle } from "@remotion/bundler";
|
|
4
|
-
import { getRenderProgress, renderMediaOnLambda } from "@remotion/lambda";
|
|
5
|
-
import { getCompositions, renderMedia, renderStill } from "@remotion/renderer";
|
|
6
|
-
import { config } from "../config.js";
|
|
7
|
-
import { createId } from "../lib/ids.js";
|
|
8
|
-
const REQUIRED_VIDEO_FPS = 30;
|
|
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;
|
|
19
|
-
export class RemotionService {
|
|
20
|
-
async render(input) {
|
|
21
|
-
const fps = input.fps ?? REQUIRED_VIDEO_FPS;
|
|
22
|
-
if (fps !== REQUIRED_VIDEO_FPS) {
|
|
23
|
-
throw new Error(`Vidfarm renders must be ${REQUIRED_VIDEO_FPS}fps. Received ${fps}fps.`);
|
|
24
|
-
}
|
|
25
|
-
const preflightAssets = await preflightRenderAssets(input.preflightUrls ?? collectPreflightUrls(input.inputProps), { requireRemoteHttpUrls: Boolean(input.preflightUrls) });
|
|
26
|
-
const serveUrl = nonBlank(input.serveUrl) ??
|
|
27
|
-
nonBlank(config.REMOTION_SERVE_URL) ??
|
|
28
|
-
inferHostedServeUrl(input.compositionId);
|
|
29
|
-
const hasHostedLambdaConfig = Boolean(nonBlank(config.REMOTION_FUNCTION_NAME) && serveUrl && nonBlank(config.REMOTION_BUCKET_NAME));
|
|
30
|
-
if (config.isProduction && config.REMOTION_MODE !== "lambda") {
|
|
31
|
-
throw new Error("Production video rendering must use lambda mode.");
|
|
32
|
-
}
|
|
33
|
-
if (input.entryPoint
|
|
34
|
-
&& (config.REMOTION_MODE === "local"
|
|
35
|
-
|| (config.REMOTION_MODE === "auto" && !hasHostedLambdaConfig)
|
|
36
|
-
|| (!config.isProduction && config.REMOTION_MODE === "auto"))) {
|
|
37
|
-
if (config.isProduction) {
|
|
38
|
-
throw new Error("Production video rendering cannot fall back to local mode.");
|
|
39
|
-
}
|
|
40
|
-
return this.renderLocal({
|
|
41
|
-
compositionId: input.compositionId,
|
|
42
|
-
entryPoint: input.entryPoint,
|
|
43
|
-
outputKey: input.outputKey,
|
|
44
|
-
fps,
|
|
45
|
-
inputProps: input.inputProps
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
if (config.REMOTION_MODE === "mock") {
|
|
49
|
-
if (config.isProduction) {
|
|
50
|
-
throw new Error("Production video rendering cannot use mock mode.");
|
|
51
|
-
}
|
|
52
|
-
return {
|
|
53
|
-
renderId: createId("render"),
|
|
54
|
-
outputUrl: null,
|
|
55
|
-
metadata: {
|
|
56
|
-
mode: "mock",
|
|
57
|
-
compositionId: input.compositionId,
|
|
58
|
-
fps,
|
|
59
|
-
inputProps: input.inputProps
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
if (!hasHostedLambdaConfig) {
|
|
64
|
-
throw new Error(config.isProduction
|
|
65
|
-
? "Cloud render is not configured."
|
|
66
|
-
: "Cloud render is not configured. Set REMOTION_FUNCTION_NAME, REMOTION_SERVE_URL, and REMOTION_BUCKET_NAME, or provide a local entryPoint.");
|
|
67
|
-
}
|
|
68
|
-
const hostedServeUrl = serveUrl;
|
|
69
|
-
const hostedFunctionName = nonBlank(config.REMOTION_FUNCTION_NAME);
|
|
70
|
-
const hostedBucketName = nonBlank(config.REMOTION_BUCKET_NAME);
|
|
71
|
-
const requestedFramesPerLambda = Number.isFinite(input.framesPerLambda)
|
|
72
|
-
? Math.max(1, Math.floor(input.framesPerLambda))
|
|
73
|
-
: 40;
|
|
74
|
-
const composition = await this.resolveComposition({
|
|
75
|
-
compositionId: input.compositionId,
|
|
76
|
-
source: hostedServeUrl,
|
|
77
|
-
inputProps: input.inputProps,
|
|
78
|
-
});
|
|
79
|
-
this.assertExpectedFps({
|
|
80
|
-
compositionId: input.compositionId,
|
|
81
|
-
actualFps: composition.fps,
|
|
82
|
-
expectedFps: fps
|
|
83
|
-
});
|
|
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({
|
|
110
|
-
region: config.REMOTION_REGION,
|
|
111
|
-
functionName: hostedFunctionName,
|
|
112
|
-
bucketName: hostedBucketName,
|
|
113
|
-
renderId: render.renderId
|
|
114
|
-
});
|
|
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.`);
|
|
122
|
-
}
|
|
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
|
-
};
|
|
148
|
-
}
|
|
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
|
-
}
|
|
154
|
-
}
|
|
155
|
-
async renderLocal(input) {
|
|
156
|
-
const renderId = createId("render");
|
|
157
|
-
const bundled = await getOrCreateBundle(input.entryPoint);
|
|
158
|
-
const composition = await this.resolveComposition({
|
|
159
|
-
source: bundled,
|
|
160
|
-
compositionId: input.compositionId,
|
|
161
|
-
inputProps: input.inputProps
|
|
162
|
-
});
|
|
163
|
-
this.assertExpectedFps({
|
|
164
|
-
compositionId: input.compositionId,
|
|
165
|
-
actualFps: composition.fps,
|
|
166
|
-
expectedFps: input.fps
|
|
167
|
-
});
|
|
168
|
-
const outputKey = input.outputKey ?? `renders/${renderId}.mp4`;
|
|
169
|
-
const outputLocation = path.join(config.VIDFARM_DATA_DIR, "storage", outputKey);
|
|
170
|
-
mkdirSync(path.dirname(outputLocation), { recursive: true });
|
|
171
|
-
await renderMedia({
|
|
172
|
-
serveUrl: bundled,
|
|
173
|
-
composition,
|
|
174
|
-
codec: "h264",
|
|
175
|
-
inputProps: input.inputProps,
|
|
176
|
-
outputLocation,
|
|
177
|
-
overwrite: true
|
|
178
|
-
});
|
|
179
|
-
return {
|
|
180
|
-
renderId,
|
|
181
|
-
outputUrl: `${config.PUBLIC_BASE_URL}/storage/${encodeURIComponent(outputKey)}`,
|
|
182
|
-
metadata: {
|
|
183
|
-
mode: "local",
|
|
184
|
-
compositionId: input.compositionId,
|
|
185
|
-
entryPoint: input.entryPoint,
|
|
186
|
-
fps: input.fps,
|
|
187
|
-
outputKey
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
async renderStill(input) {
|
|
192
|
-
const renderId = createId("render");
|
|
193
|
-
const bundled = await getOrCreateBundle(input.entryPoint);
|
|
194
|
-
const composition = await this.resolveComposition({
|
|
195
|
-
source: bundled,
|
|
196
|
-
compositionId: input.compositionId,
|
|
197
|
-
inputProps: input.inputProps
|
|
198
|
-
});
|
|
199
|
-
const format = input.imageFormat ?? "png";
|
|
200
|
-
const extension = format === "jpeg" ? "jpg" : format;
|
|
201
|
-
const outputKey = input.outputKey ?? `renders/${renderId}.${extension}`;
|
|
202
|
-
const outputLocation = path.join(config.VIDFARM_DATA_DIR, "storage", outputKey);
|
|
203
|
-
mkdirSync(path.dirname(outputLocation), { recursive: true });
|
|
204
|
-
await renderStill({
|
|
205
|
-
serveUrl: bundled,
|
|
206
|
-
composition,
|
|
207
|
-
inputProps: input.inputProps,
|
|
208
|
-
output: outputLocation,
|
|
209
|
-
imageFormat: format,
|
|
210
|
-
overwrite: true
|
|
211
|
-
});
|
|
212
|
-
return {
|
|
213
|
-
renderId,
|
|
214
|
-
outputUrl: `${config.PUBLIC_BASE_URL}/storage/${encodeURIComponent(outputKey)}`,
|
|
215
|
-
metadata: {
|
|
216
|
-
mode: "local_still",
|
|
217
|
-
compositionId: input.compositionId,
|
|
218
|
-
entryPoint: input.entryPoint,
|
|
219
|
-
outputKey,
|
|
220
|
-
imageFormat: format
|
|
221
|
-
}
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
async resolveComposition(input) {
|
|
225
|
-
const compositions = await getCompositions(input.source, {
|
|
226
|
-
inputProps: input.inputProps
|
|
227
|
-
});
|
|
228
|
-
const composition = compositions.find((item) => item.id === input.compositionId);
|
|
229
|
-
if (!composition) {
|
|
230
|
-
throw new Error(`Composition ${input.compositionId} not found in ${input.source}`);
|
|
231
|
-
}
|
|
232
|
-
return composition;
|
|
233
|
-
}
|
|
234
|
-
assertExpectedFps(input) {
|
|
235
|
-
if (input.actualFps !== input.expectedFps) {
|
|
236
|
-
throw new Error(`Composition ${input.compositionId} is ${input.actualFps}fps, but Vidfarm requires ${input.expectedFps}fps output.`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
const bundleCache = new Map();
|
|
241
|
-
function getOrCreateBundle(entryPoint) {
|
|
242
|
-
const existing = bundleCache.get(entryPoint);
|
|
243
|
-
if (existing) {
|
|
244
|
-
return existing;
|
|
245
|
-
}
|
|
246
|
-
const created = bundle({
|
|
247
|
-
entryPoint,
|
|
248
|
-
onProgress: () => undefined
|
|
249
|
-
});
|
|
250
|
-
bundleCache.set(entryPoint, created);
|
|
251
|
-
return created;
|
|
252
|
-
}
|
|
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 }] : [];
|
|
259
|
-
}
|
|
260
|
-
if (Array.isArray(value)) {
|
|
261
|
-
return value.flatMap((item, index) => collectPreflightUrls(item, `${path}[${index}]`));
|
|
262
|
-
}
|
|
263
|
-
if (!value || typeof value !== "object") {
|
|
264
|
-
return [];
|
|
265
|
-
}
|
|
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("; ")}`);
|
|
276
|
-
}
|
|
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);
|
|
290
|
-
}
|
|
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;
|
|
310
|
-
}
|
|
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;
|
|
323
|
-
}
|
|
324
|
-
if (attempt < maxAttempts - 1) {
|
|
325
|
-
await sleep(preflightRetryDelayMs(attempt));
|
|
326
|
-
}
|
|
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 };
|
|
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);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
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;
|
|
674
|
-
}
|
|
675
|
-
function nonBlank(value) {
|
|
676
|
-
const trimmed = value?.trim();
|
|
677
|
-
return trimmed ? trimmed : undefined;
|
|
678
|
-
}
|
|
679
|
-
function inferHostedServeUrl(compositionId) {
|
|
680
|
-
const bucketName = nonBlank(config.REMOTION_BUCKET_NAME);
|
|
681
|
-
if (!bucketName) {
|
|
682
|
-
return undefined;
|
|
683
|
-
}
|
|
684
|
-
const siteName = nonBlank(config.REMOTION_SITE_NAME) ?? compositionId;
|
|
685
|
-
const encodedSiteName = siteName.split("/").map(encodeURIComponent).join("/");
|
|
686
|
-
return `https://${bucketName}.s3.${config.REMOTION_REGION}.amazonaws.com/sites/${encodedSiteName}/index.html`;
|
|
687
|
-
}
|
|
688
|
-
//# sourceMappingURL=remotion.js.map
|