@remotion/serverless 4.0.509 → 4.0.510
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/dist/create-post-render-data.js +4 -3
- package/dist/handlers/launch.js +25 -1
- package/dist/handlers/renderer.d.ts +7 -1
- package/dist/handlers/renderer.js +20 -11
- package/dist/handlers/still.js +1 -1
- package/dist/inner-routine.js +39 -15
- package/dist/s3-renderer-output.d.ts +21 -0
- package/dist/s3-renderer-output.js +186 -0
- package/dist/stream-renderer.d.ts +2 -1
- package/dist/stream-renderer.js +231 -6
- package/package.json +6 -6
|
@@ -22,6 +22,7 @@ const createPostRenderData = ({ region, memorySizeInMb, renderMetadata, errorExp
|
|
|
22
22
|
throw new Error('Cannot wrap up without an output file in the S3 bucket.');
|
|
23
23
|
}
|
|
24
24
|
const endTime = Date.now();
|
|
25
|
+
const billingCurrency = providerSpecifics.getBillingCurrency(region);
|
|
25
26
|
if (overallProgress.timeToEncode === null) {
|
|
26
27
|
throw new Error('Expected time to encode to be set');
|
|
27
28
|
}
|
|
@@ -30,11 +31,11 @@ const createPostRenderData = ({ region, memorySizeInMb, renderMetadata, errorExp
|
|
|
30
31
|
}
|
|
31
32
|
return {
|
|
32
33
|
cost: {
|
|
33
|
-
currency:
|
|
34
|
+
currency: billingCurrency,
|
|
34
35
|
disclaimer: 'Estimated cost for function invocations only. Does not include cost for storage and data transfer.',
|
|
35
36
|
estimatedCost: cost,
|
|
36
|
-
estimatedDisplayCost:
|
|
37
|
-
currency:
|
|
37
|
+
estimatedDisplayCost: `${billingCurrency === 'CNY' ? '¥' : '$'}${new Intl.NumberFormat('en-US', {
|
|
38
|
+
currency: billingCurrency,
|
|
38
39
|
currencyDisplay: 'narrowSymbol',
|
|
39
40
|
}).format(cost)}`,
|
|
40
41
|
},
|
package/dist/handlers/launch.js
CHANGED
|
@@ -351,7 +351,7 @@ const innerLaunchHandler = async ({ params, options, overallProgress, registerCl
|
|
|
351
351
|
return { alreadyExisted: false };
|
|
352
352
|
};
|
|
353
353
|
await Promise.all(lambdaPayloads.map(async (payload) => {
|
|
354
|
-
await (0, stream_renderer_1.
|
|
354
|
+
await (0, stream_renderer_1.renderRendererFunctionWithRetry)({
|
|
355
355
|
files,
|
|
356
356
|
functionName: rendererFunctionName,
|
|
357
357
|
outdir,
|
|
@@ -362,6 +362,7 @@ const innerLaunchHandler = async ({ params, options, overallProgress, registerCl
|
|
|
362
362
|
providerSpecifics,
|
|
363
363
|
insideFunctionSpecifics,
|
|
364
364
|
requestHandler: null,
|
|
365
|
+
expectedBucketOwner: options.expectedBucketOwner,
|
|
365
366
|
});
|
|
366
367
|
}));
|
|
367
368
|
const postRenderData = await (0, merge_chunks_1.mergeChunksAndFinishRender)({
|
|
@@ -401,6 +402,29 @@ const innerLaunchHandler = async ({ params, options, overallProgress, registerCl
|
|
|
401
402
|
requestHandler: null,
|
|
402
403
|
sampleRate: params.sampleRate,
|
|
403
404
|
});
|
|
405
|
+
if (providerSpecifics.getRendererFunctionTransport(insideFunctionSpecifics.getCurrentRegionInFunction()) === 's3') {
|
|
406
|
+
try {
|
|
407
|
+
const transportObjects = await providerSpecifics.listObjects({
|
|
408
|
+
bucketName: params.bucketName,
|
|
409
|
+
prefix: (0, serverless_client_2.rendererTransportPrefix)(params.renderId),
|
|
410
|
+
region: insideFunctionSpecifics.getCurrentRegionInFunction(),
|
|
411
|
+
expectedBucketOwner: options.expectedBucketOwner,
|
|
412
|
+
forcePathStyle: params.forcePathStyle,
|
|
413
|
+
requestHandler: null,
|
|
414
|
+
});
|
|
415
|
+
await Promise.all(transportObjects.map((object) => providerSpecifics.deleteFile({
|
|
416
|
+
bucketName: params.bucketName,
|
|
417
|
+
key: object.Key,
|
|
418
|
+
region: insideFunctionSpecifics.getCurrentRegionInFunction(),
|
|
419
|
+
customCredentials: null,
|
|
420
|
+
forcePathStyle: params.forcePathStyle,
|
|
421
|
+
requestHandler: null,
|
|
422
|
+
})));
|
|
423
|
+
}
|
|
424
|
+
catch (err) {
|
|
425
|
+
renderer_1.RenderInternals.Log.warn({ indent: false, logLevel: params.logLevel }, 'Could not clean up renderer transport objects', err);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
404
428
|
return postRenderData;
|
|
405
429
|
};
|
|
406
430
|
const launchHandler = async ({ params, options, providerSpecifics, insideFunctionSpecifics, }) => {
|
|
@@ -9,12 +9,18 @@ export type RequestContext = {
|
|
|
9
9
|
getRemainingTimeInMillis: () => number;
|
|
10
10
|
awsRequestId: string;
|
|
11
11
|
};
|
|
12
|
-
export declare const rendererHandler: <Provider extends CloudProvider<string, Record<string, unknown>, Record<string, unknown>, string, object>>({ onStream, options, params, providerSpecifics, requestContext, insideFunctionSpecifics, }: {
|
|
12
|
+
export declare const rendererHandler: <Provider extends CloudProvider<string, Record<string, unknown>, Record<string, unknown>, string, object>>({ onStream, options, params, providerSpecifics, requestContext, insideFunctionSpecifics, onMediaFiles, }: {
|
|
13
13
|
params: ServerlessPayload<Provider>;
|
|
14
14
|
options: Options;
|
|
15
15
|
onStream: OnStream<Provider>;
|
|
16
16
|
requestContext: RequestContext;
|
|
17
17
|
providerSpecifics: ProviderSpecifics<Provider>;
|
|
18
18
|
insideFunctionSpecifics: InsideFunctionSpecifics<Provider>;
|
|
19
|
+
onMediaFiles: ((options: {
|
|
20
|
+
videoOutputLocation: string;
|
|
21
|
+
audioOutputLocation: string | null;
|
|
22
|
+
isAudioOnly: boolean;
|
|
23
|
+
completedAt: number;
|
|
24
|
+
}) => Promise<void>) | null;
|
|
19
25
|
}) => Promise<void>;
|
|
20
26
|
export {};
|
|
@@ -13,7 +13,7 @@ const get_tmp_dir_1 = require("../get-tmp-dir");
|
|
|
13
13
|
const leak_detection_1 = require("../leak-detection");
|
|
14
14
|
const on_downloads_helpers_1 = require("../on-downloads-helpers");
|
|
15
15
|
const why_is_node_running_1 = require("../why-is-node-running");
|
|
16
|
-
const renderHandler = async ({ params, options, logs, onStream, providerSpecifics, insideFunctionSpecifics, onBrowserInstance, }) => {
|
|
16
|
+
const renderHandler = async ({ params, options, logs, onStream, providerSpecifics, insideFunctionSpecifics, onBrowserInstance, onMediaFiles, }) => {
|
|
17
17
|
if (params.type !== serverless_client_1.ServerlessRoutines.renderer) {
|
|
18
18
|
throw new Error('Params must be renderer');
|
|
19
19
|
}
|
|
@@ -247,15 +247,24 @@ const renderHandler = async ({ params, options, logs, onStream, providerSpecific
|
|
|
247
247
|
.catch((err) => reject(err));
|
|
248
248
|
});
|
|
249
249
|
const streamTimer = insideFunctionSpecifics.timer('Streaming chunk to the main function', params.logLevel);
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
await
|
|
253
|
-
|
|
254
|
-
|
|
250
|
+
const endRendered = Date.now();
|
|
251
|
+
if (onMediaFiles) {
|
|
252
|
+
await onMediaFiles({
|
|
253
|
+
videoOutputLocation,
|
|
254
|
+
audioOutputLocation,
|
|
255
|
+
isAudioOnly: pure_1.NoReactAPIs.isAudioCodec(params.codec),
|
|
256
|
+
completedAt: endRendered,
|
|
255
257
|
});
|
|
256
|
-
audioChunkTimer.end();
|
|
257
258
|
}
|
|
258
|
-
|
|
259
|
+
else {
|
|
260
|
+
if (audioOutputLocation) {
|
|
261
|
+
const audioChunkTimer = insideFunctionSpecifics.timer('Sending audio chunk', params.logLevel);
|
|
262
|
+
await onStream({
|
|
263
|
+
type: 'audio-chunk-rendered',
|
|
264
|
+
payload: new Uint8Array(node_fs_1.default.readFileSync(audioOutputLocation)),
|
|
265
|
+
});
|
|
266
|
+
audioChunkTimer.end();
|
|
267
|
+
}
|
|
259
268
|
const videoChunkTimer = insideFunctionSpecifics.timer('Sending main chunk', params.logLevel);
|
|
260
269
|
await onStream({
|
|
261
270
|
type: pure_1.NoReactAPIs.isAudioCodec(params.codec)
|
|
@@ -265,7 +274,6 @@ const renderHandler = async ({ params, options, logs, onStream, providerSpecific
|
|
|
265
274
|
});
|
|
266
275
|
videoChunkTimer.end();
|
|
267
276
|
}
|
|
268
|
-
const endRendered = Date.now();
|
|
269
277
|
await onStream({
|
|
270
278
|
type: 'chunk-complete',
|
|
271
279
|
payload: {
|
|
@@ -286,7 +294,7 @@ const renderHandler = async ({ params, options, logs, onStream, providerSpecific
|
|
|
286
294
|
return {};
|
|
287
295
|
};
|
|
288
296
|
const ENABLE_SLOW_LEAK_DETECTION = false;
|
|
289
|
-
const rendererHandler = async ({ onStream, options, params, providerSpecifics, requestContext, insideFunctionSpecifics, }) => {
|
|
297
|
+
const rendererHandler = async ({ onStream, options, params, providerSpecifics, requestContext, insideFunctionSpecifics, onMediaFiles, }) => {
|
|
290
298
|
if (params.type !== serverless_client_1.ServerlessRoutines.renderer) {
|
|
291
299
|
throw new Error('Params must be renderer');
|
|
292
300
|
}
|
|
@@ -305,6 +313,7 @@ const rendererHandler = async ({ onStream, options, params, providerSpecifics, r
|
|
|
305
313
|
onBrowserInstance: (browserInstance) => {
|
|
306
314
|
instance = browserInstance;
|
|
307
315
|
},
|
|
316
|
+
onMediaFiles,
|
|
308
317
|
});
|
|
309
318
|
}
|
|
310
319
|
catch (err) {
|
|
@@ -323,7 +332,7 @@ const rendererHandler = async ({ onStream, options, params, providerSpecifics, r
|
|
|
323
332
|
const shouldRetry = isRetryableError && params.retriesLeft > 0 && !shouldNotRetry;
|
|
324
333
|
renderer_1.RenderInternals.Log.error({ indent: false, logLevel: params.logLevel }, `Error occurred (will retry = ${String(shouldRetry)})`);
|
|
325
334
|
renderer_1.RenderInternals.Log.error({ indent: false, logLevel: params.logLevel }, err.stack);
|
|
326
|
-
onStream({
|
|
335
|
+
await onStream({
|
|
327
336
|
type: 'error-occurred',
|
|
328
337
|
payload: {
|
|
329
338
|
error: err.stack,
|
package/dist/handlers/still.js
CHANGED
|
@@ -303,7 +303,7 @@ const innerStillHandler = async ({ params, expectedBucketOwner, renderId, onStre
|
|
|
303
303
|
size,
|
|
304
304
|
sizeInBytes: size,
|
|
305
305
|
bucketName,
|
|
306
|
-
estimatedPrice: (0, serverless_client_1.formatCostsInfo)(estimatedPrice),
|
|
306
|
+
estimatedPrice: (0, serverless_client_1.formatCostsInfo)(estimatedPrice, providerSpecifics.getBillingCurrency(insideFunctionSpecifics.getCurrentRegionInFunction())),
|
|
307
307
|
renderId,
|
|
308
308
|
outKey,
|
|
309
309
|
receivedArtifacts: receivedArtifact,
|
package/dist/inner-routine.js
CHANGED
|
@@ -13,6 +13,7 @@ const info_1 = require("./info");
|
|
|
13
13
|
const is_warm_1 = require("./is-warm");
|
|
14
14
|
const leak_detection_1 = require("./leak-detection");
|
|
15
15
|
const print_logging_grep_helper_1 = require("./print-logging-grep-helper");
|
|
16
|
+
const s3_renderer_output_1 = require("./s3-renderer-output");
|
|
16
17
|
const innerHandler = async ({ params, responseWriter, context, providerSpecifics, insideFunctionSpecifics, }) => {
|
|
17
18
|
(0, leak_detection_1.setCurrentRequestId)(context.awsRequestId);
|
|
18
19
|
process.env.__RESERVED_IS_INSIDE_REMOTION_LAMBDA = 'true';
|
|
@@ -81,6 +82,14 @@ const innerHandler = async ({ params, responseWriter, context, providerSpecifics
|
|
|
81
82
|
timeoutInMilliseconds,
|
|
82
83
|
providerSpecifics,
|
|
83
84
|
insideFunctionSpecifics,
|
|
85
|
+
})
|
|
86
|
+
.then((r) => {
|
|
87
|
+
if (!params.streamed && r.type === 'error') {
|
|
88
|
+
return responseWriter
|
|
89
|
+
.write(new TextEncoder().encode(JSON.stringify(r)))
|
|
90
|
+
.then(() => r);
|
|
91
|
+
}
|
|
92
|
+
return r;
|
|
84
93
|
})
|
|
85
94
|
.then((r) => {
|
|
86
95
|
resolve(r);
|
|
@@ -183,29 +192,44 @@ const innerHandler = async ({ params, responseWriter, context, providerSpecifics
|
|
|
183
192
|
isWarm,
|
|
184
193
|
}, params.logLevel);
|
|
185
194
|
}
|
|
195
|
+
const region = insideFunctionSpecifics.getCurrentRegionInFunction();
|
|
196
|
+
const transport = providerSpecifics.getRendererFunctionTransport(region);
|
|
197
|
+
const s3Output = transport === 's3'
|
|
198
|
+
? (0, s3_renderer_output_1.makeS3RendererOutput)({
|
|
199
|
+
params,
|
|
200
|
+
expectedBucketOwner: currentUserId,
|
|
201
|
+
region,
|
|
202
|
+
providerSpecifics,
|
|
203
|
+
})
|
|
204
|
+
: null;
|
|
205
|
+
await (s3Output === null || s3Output === void 0 ? void 0 : s3Output.initialize());
|
|
186
206
|
await new Promise((resolve, reject) => {
|
|
207
|
+
var _a;
|
|
187
208
|
(0, renderer_2.rendererHandler)({
|
|
188
209
|
params,
|
|
189
210
|
options: {
|
|
190
211
|
expectedBucketOwner: currentUserId,
|
|
191
212
|
isWarm,
|
|
192
213
|
},
|
|
193
|
-
onStream:
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return new Promise((innerResolve, innerReject) => {
|
|
199
|
-
writeProm
|
|
200
|
-
.then(() => {
|
|
201
|
-
innerResolve();
|
|
202
|
-
})
|
|
203
|
-
.catch((err) => {
|
|
204
|
-
reject(err);
|
|
205
|
-
innerReject(err);
|
|
214
|
+
onStream: s3Output
|
|
215
|
+
? s3Output.onStream
|
|
216
|
+
: (payload) => {
|
|
217
|
+
const message = (0, serverless_client_1.makeStreamPayload)({
|
|
218
|
+
message: payload,
|
|
206
219
|
});
|
|
207
|
-
|
|
208
|
-
|
|
220
|
+
const writeProm = responseWriter.write(message);
|
|
221
|
+
return new Promise((innerResolve, innerReject) => {
|
|
222
|
+
writeProm
|
|
223
|
+
.then(() => {
|
|
224
|
+
innerResolve();
|
|
225
|
+
})
|
|
226
|
+
.catch((err) => {
|
|
227
|
+
reject(err);
|
|
228
|
+
innerReject(err);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
},
|
|
232
|
+
onMediaFiles: (_a = s3Output === null || s3Output === void 0 ? void 0 : s3Output.uploadMediaAndComplete) !== null && _a !== void 0 ? _a : null,
|
|
209
233
|
requestContext: context,
|
|
210
234
|
providerSpecifics,
|
|
211
235
|
insideFunctionSpecifics,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { EmittedArtifact } from '@remotion/renderer';
|
|
2
|
+
import type { CloudProvider, OnStream, ProviderSpecifics, S3RendererArtifactMetadata, ServerlessPayload } from '@remotion/serverless-client';
|
|
3
|
+
export declare const makeS3RendererOutput: <Provider extends CloudProvider<string, Record<string, unknown>, Record<string, unknown>, string, object>>({ params, expectedBucketOwner, region, providerSpecifics, }: {
|
|
4
|
+
params: ServerlessPayload<Provider>;
|
|
5
|
+
expectedBucketOwner: string;
|
|
6
|
+
region: Provider["region"];
|
|
7
|
+
providerSpecifics: ProviderSpecifics<Provider>;
|
|
8
|
+
}) => {
|
|
9
|
+
initialize: () => Promise<void>;
|
|
10
|
+
onStream: OnStream<Provider>;
|
|
11
|
+
uploadMediaAndComplete: ({ videoOutputLocation, audioOutputLocation, isAudioOnly, completedAt, }: {
|
|
12
|
+
videoOutputLocation: string;
|
|
13
|
+
audioOutputLocation: string | null;
|
|
14
|
+
isAudioOnly: boolean;
|
|
15
|
+
completedAt: number;
|
|
16
|
+
}) => Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
export declare const artifactFromS3: ({ metadata, content, }: {
|
|
19
|
+
metadata: S3RendererArtifactMetadata;
|
|
20
|
+
content: Uint8Array<ArrayBufferLike>;
|
|
21
|
+
}) => EmittedArtifact;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.artifactFromS3 = exports.makeS3RendererOutput = void 0;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const serverless_client_1 = require("@remotion/serverless-client");
|
|
6
|
+
const makeS3RendererOutput = ({ params, expectedBucketOwner, region, providerSpecifics, }) => {
|
|
7
|
+
if (params.type !== serverless_client_1.ServerlessRoutines.renderer) {
|
|
8
|
+
throw new Error('Expected renderer payload');
|
|
9
|
+
}
|
|
10
|
+
const startedAt = Date.now();
|
|
11
|
+
let lambdaInvoked = true;
|
|
12
|
+
let renderedFrames = 0;
|
|
13
|
+
let encodedFrames = 0;
|
|
14
|
+
let writeQueue = Promise.resolve();
|
|
15
|
+
const artifacts = [];
|
|
16
|
+
const artifactUploads = [];
|
|
17
|
+
const statusKey = (0, serverless_client_1.rendererTransportStatusKey)({
|
|
18
|
+
renderId: params.renderId,
|
|
19
|
+
chunk: params.chunk,
|
|
20
|
+
attempt: params.attempt,
|
|
21
|
+
});
|
|
22
|
+
const writeStatus = (status) => {
|
|
23
|
+
writeQueue = writeQueue.then(() => providerSpecifics.writeFile({
|
|
24
|
+
bucketName: params.bucketName,
|
|
25
|
+
key: statusKey,
|
|
26
|
+
body: JSON.stringify(status),
|
|
27
|
+
region,
|
|
28
|
+
privacy: 'private',
|
|
29
|
+
expectedBucketOwner,
|
|
30
|
+
downloadBehavior: null,
|
|
31
|
+
customCredentials: null,
|
|
32
|
+
forcePathStyle: params.forcePathStyle,
|
|
33
|
+
storageClass: null,
|
|
34
|
+
requestHandler: null,
|
|
35
|
+
}));
|
|
36
|
+
return writeQueue;
|
|
37
|
+
};
|
|
38
|
+
const runningStatus = () => ({
|
|
39
|
+
schema: 1,
|
|
40
|
+
state: 'running',
|
|
41
|
+
chunk: params.chunk,
|
|
42
|
+
attempt: params.attempt,
|
|
43
|
+
lambdaInvoked,
|
|
44
|
+
renderedFrames,
|
|
45
|
+
encodedFrames,
|
|
46
|
+
startedAt,
|
|
47
|
+
});
|
|
48
|
+
const initialize = () => writeStatus(runningStatus());
|
|
49
|
+
const onStream = async (message) => {
|
|
50
|
+
if (message.type === 'lambda-invoked') {
|
|
51
|
+
lambdaInvoked = true;
|
|
52
|
+
await writeStatus(runningStatus());
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (message.type === 'frames-rendered') {
|
|
56
|
+
renderedFrames = Math.max(renderedFrames, message.payload.rendered);
|
|
57
|
+
encodedFrames = Math.max(encodedFrames, message.payload.encoded);
|
|
58
|
+
await writeStatus(runningStatus());
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (message.type === 'artifact-emitted') {
|
|
62
|
+
const artifact = (0, serverless_client_1.deserializeArtifact)(message.payload.artifact);
|
|
63
|
+
const key = (0, serverless_client_1.rendererTransportArtifactKey)({
|
|
64
|
+
renderId: params.renderId,
|
|
65
|
+
chunk: params.chunk,
|
|
66
|
+
attempt: params.attempt,
|
|
67
|
+
}, artifacts.length);
|
|
68
|
+
const metadata = {
|
|
69
|
+
filename: artifact.filename,
|
|
70
|
+
frame: artifact.frame,
|
|
71
|
+
binary: artifact.content instanceof Uint8Array,
|
|
72
|
+
downloadBehavior: artifact.downloadBehavior,
|
|
73
|
+
};
|
|
74
|
+
artifacts.push({ key, metadata });
|
|
75
|
+
const upload = providerSpecifics.writeFile({
|
|
76
|
+
bucketName: params.bucketName,
|
|
77
|
+
key,
|
|
78
|
+
body: artifact.content,
|
|
79
|
+
region,
|
|
80
|
+
privacy: 'private',
|
|
81
|
+
expectedBucketOwner,
|
|
82
|
+
downloadBehavior: null,
|
|
83
|
+
customCredentials: null,
|
|
84
|
+
forcePathStyle: params.forcePathStyle,
|
|
85
|
+
storageClass: null,
|
|
86
|
+
requestHandler: null,
|
|
87
|
+
});
|
|
88
|
+
artifactUploads.push(upload);
|
|
89
|
+
await upload;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (message.type === 'error-occurred') {
|
|
93
|
+
await writeStatus({
|
|
94
|
+
schema: 1,
|
|
95
|
+
state: 'failed',
|
|
96
|
+
chunk: params.chunk,
|
|
97
|
+
attempt: params.attempt,
|
|
98
|
+
lambdaInvoked,
|
|
99
|
+
renderedFrames,
|
|
100
|
+
encodedFrames,
|
|
101
|
+
startedAt,
|
|
102
|
+
failedAt: Date.now(),
|
|
103
|
+
errorInfo: message.payload.errorInfo,
|
|
104
|
+
shouldRetry: message.payload.shouldRetry,
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (message.type === 'chunk-complete') {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
throw new Error(`Unexpected ${message.type} event in S3 renderer transport`);
|
|
112
|
+
};
|
|
113
|
+
const uploadMediaAndComplete = async ({ videoOutputLocation, audioOutputLocation, isAudioOnly, completedAt, }) => {
|
|
114
|
+
const videoKey = isAudioOnly
|
|
115
|
+
? null
|
|
116
|
+
: (0, serverless_client_1.rendererTransportVideoKey)({
|
|
117
|
+
renderId: params.renderId,
|
|
118
|
+
chunk: params.chunk,
|
|
119
|
+
attempt: params.attempt,
|
|
120
|
+
});
|
|
121
|
+
const audioKey = audioOutputLocation || isAudioOnly
|
|
122
|
+
? (0, serverless_client_1.rendererTransportAudioKey)({
|
|
123
|
+
renderId: params.renderId,
|
|
124
|
+
chunk: params.chunk,
|
|
125
|
+
attempt: params.attempt,
|
|
126
|
+
})
|
|
127
|
+
: null;
|
|
128
|
+
await Promise.all([
|
|
129
|
+
...artifactUploads,
|
|
130
|
+
videoKey
|
|
131
|
+
? providerSpecifics.writeFile({
|
|
132
|
+
bucketName: params.bucketName,
|
|
133
|
+
key: videoKey,
|
|
134
|
+
body: (0, node_fs_1.createReadStream)(videoOutputLocation),
|
|
135
|
+
region,
|
|
136
|
+
privacy: 'private',
|
|
137
|
+
expectedBucketOwner,
|
|
138
|
+
downloadBehavior: null,
|
|
139
|
+
customCredentials: null,
|
|
140
|
+
forcePathStyle: params.forcePathStyle,
|
|
141
|
+
storageClass: null,
|
|
142
|
+
requestHandler: null,
|
|
143
|
+
})
|
|
144
|
+
: null,
|
|
145
|
+
audioKey
|
|
146
|
+
? providerSpecifics.writeFile({
|
|
147
|
+
bucketName: params.bucketName,
|
|
148
|
+
key: audioKey,
|
|
149
|
+
body: (0, node_fs_1.createReadStream)(audioOutputLocation !== null && audioOutputLocation !== void 0 ? audioOutputLocation : videoOutputLocation),
|
|
150
|
+
region,
|
|
151
|
+
privacy: 'private',
|
|
152
|
+
expectedBucketOwner,
|
|
153
|
+
downloadBehavior: null,
|
|
154
|
+
customCredentials: null,
|
|
155
|
+
forcePathStyle: params.forcePathStyle,
|
|
156
|
+
storageClass: null,
|
|
157
|
+
requestHandler: null,
|
|
158
|
+
})
|
|
159
|
+
: null,
|
|
160
|
+
]);
|
|
161
|
+
lambdaInvoked = true;
|
|
162
|
+
await writeStatus({
|
|
163
|
+
schema: 1,
|
|
164
|
+
state: 'completed',
|
|
165
|
+
chunk: params.chunk,
|
|
166
|
+
attempt: params.attempt,
|
|
167
|
+
lambdaInvoked: true,
|
|
168
|
+
renderedFrames,
|
|
169
|
+
encodedFrames,
|
|
170
|
+
startedAt,
|
|
171
|
+
completedAt,
|
|
172
|
+
videoKey,
|
|
173
|
+
audioKey,
|
|
174
|
+
artifacts,
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
return { initialize, onStream, uploadMediaAndComplete };
|
|
178
|
+
};
|
|
179
|
+
exports.makeS3RendererOutput = makeS3RendererOutput;
|
|
180
|
+
const artifactFromS3 = ({ metadata, content, }) => ({
|
|
181
|
+
filename: metadata.filename,
|
|
182
|
+
frame: metadata.frame,
|
|
183
|
+
downloadBehavior: metadata.downloadBehavior,
|
|
184
|
+
content: metadata.binary ? content : new TextDecoder().decode(content),
|
|
185
|
+
});
|
|
186
|
+
exports.artifactFromS3 = artifactFromS3;
|
|
@@ -2,7 +2,7 @@ import type { EmittedArtifact } from '@remotion/renderer';
|
|
|
2
2
|
import type { CloudProvider, ProviderSpecifics, ServerlessPayload } from '@remotion/serverless-client';
|
|
3
3
|
import type { OverallProgressHelper } from './overall-render-progress';
|
|
4
4
|
import type { InsideFunctionSpecifics } from './provider-implementation';
|
|
5
|
-
export declare const
|
|
5
|
+
export declare const renderRendererFunctionWithRetry: <Provider extends CloudProvider<string, Record<string, unknown>, Record<string, unknown>, string, object>>({ payload, files, functionName, outdir, overallProgress, logLevel, onArtifact, providerSpecifics, insideFunctionSpecifics, requestHandler, expectedBucketOwner, }: {
|
|
6
6
|
payload: ServerlessPayload<Provider>;
|
|
7
7
|
functionName: string;
|
|
8
8
|
outdir: string;
|
|
@@ -15,4 +15,5 @@ export declare const streamRendererFunctionWithRetry: <Provider extends CloudPro
|
|
|
15
15
|
providerSpecifics: ProviderSpecifics<Provider>;
|
|
16
16
|
insideFunctionSpecifics: InsideFunctionSpecifics<Provider>;
|
|
17
17
|
requestHandler: Provider["requestHandler"] | null;
|
|
18
|
+
expectedBucketOwner: string;
|
|
18
19
|
}) => Promise<unknown>;
|
package/dist/stream-renderer.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.renderRendererFunctionWithRetry = void 0;
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
|
+
const promises_1 = require("node:stream/promises");
|
|
5
6
|
const path_1 = require("path");
|
|
6
7
|
const renderer_1 = require("@remotion/renderer");
|
|
7
8
|
const serverless_client_1 = require("@remotion/serverless-client");
|
|
9
|
+
const s3_renderer_output_1 = require("./s3-renderer-output");
|
|
8
10
|
const streamRenderer = ({ payload, functionName, outdir, overallProgress, files, logLevel, onArtifact, providerSpecifics, insideFunctionSpecifics, requestHandler, }) => {
|
|
9
11
|
if (payload.type !== serverless_client_1.ServerlessRoutines.renderer) {
|
|
10
12
|
throw new Error('Expected renderer type');
|
|
@@ -110,11 +112,232 @@ const streamRenderer = ({ payload, functionName, outdir, overallProgress, files,
|
|
|
110
112
|
});
|
|
111
113
|
});
|
|
112
114
|
};
|
|
113
|
-
const
|
|
115
|
+
const wait = (duration) => new Promise((resolve) => setTimeout(resolve, duration));
|
|
116
|
+
const isMissingObjectError = (err) => {
|
|
117
|
+
const error = err;
|
|
118
|
+
return (error.name === 'NoSuchKey' ||
|
|
119
|
+
error.name === 'NotFound' ||
|
|
120
|
+
error.Code === 'NoSuchKey' ||
|
|
121
|
+
error.code === 'NoSuchKey');
|
|
122
|
+
};
|
|
123
|
+
const readStreamAsBytes = async (stream) => {
|
|
124
|
+
const chunks = [];
|
|
125
|
+
for await (const chunk of stream) {
|
|
126
|
+
chunks.push(typeof chunk === 'string'
|
|
127
|
+
? new TextEncoder().encode(chunk)
|
|
128
|
+
: new Uint8Array(chunk));
|
|
129
|
+
}
|
|
130
|
+
const size = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
|
|
131
|
+
const result = new Uint8Array(size);
|
|
132
|
+
let offset = 0;
|
|
133
|
+
for (const chunk of chunks) {
|
|
134
|
+
result.set(chunk, offset);
|
|
135
|
+
offset += chunk.byteLength;
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
};
|
|
139
|
+
const s3Renderer = async ({ payload, functionName, outdir, overallProgress, files, logLevel, onArtifact, providerSpecifics, insideFunctionSpecifics, requestHandler, expectedBucketOwner, }) => {
|
|
140
|
+
var _a, _b;
|
|
141
|
+
if (payload.type !== serverless_client_1.ServerlessRoutines.renderer) {
|
|
142
|
+
throw new Error('Expected renderer type');
|
|
143
|
+
}
|
|
144
|
+
const region = insideFunctionSpecifics.getCurrentRegionInFunction();
|
|
145
|
+
try {
|
|
146
|
+
await providerSpecifics.callFunctionAsync({
|
|
147
|
+
functionName,
|
|
148
|
+
payload,
|
|
149
|
+
region,
|
|
150
|
+
timeoutInTest: 12000,
|
|
151
|
+
type: serverless_client_1.ServerlessRoutines.renderer,
|
|
152
|
+
requestHandler,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
return {
|
|
157
|
+
type: 'error',
|
|
158
|
+
error: (_a = err.stack) !== null && _a !== void 0 ? _a : err.message,
|
|
159
|
+
shouldRetry: providerSpecifics.isFlakyError(err),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const statusKey = (0, serverless_client_1.rendererTransportStatusKey)({
|
|
163
|
+
renderId: payload.renderId,
|
|
164
|
+
chunk: payload.chunk,
|
|
165
|
+
attempt: payload.attempt,
|
|
166
|
+
});
|
|
167
|
+
let lastRendered = 0;
|
|
168
|
+
let lastEncoded = 0;
|
|
169
|
+
let lambdaInvoked = false;
|
|
170
|
+
let pollingInterval = 250;
|
|
171
|
+
const timeoutTimestamp = overallProgress.get().timeoutTimestamp - 1000;
|
|
172
|
+
while (Date.now() < timeoutTimestamp) {
|
|
173
|
+
try {
|
|
174
|
+
const statusStream = await providerSpecifics.readFile({
|
|
175
|
+
bucketName: payload.bucketName,
|
|
176
|
+
key: statusKey,
|
|
177
|
+
region,
|
|
178
|
+
expectedBucketOwner,
|
|
179
|
+
forcePathStyle: payload.forcePathStyle,
|
|
180
|
+
requestHandler,
|
|
181
|
+
});
|
|
182
|
+
const status = (0, serverless_client_1.parseS3RendererStatus)({
|
|
183
|
+
value: JSON.parse(await (0, serverless_client_1.streamToString)(statusStream)),
|
|
184
|
+
expectedChunk: payload.chunk,
|
|
185
|
+
expectedAttempt: payload.attempt,
|
|
186
|
+
});
|
|
187
|
+
if (status.lambdaInvoked && !lambdaInvoked) {
|
|
188
|
+
lambdaInvoked = true;
|
|
189
|
+
overallProgress.setLambdaInvoked(payload.chunk);
|
|
190
|
+
}
|
|
191
|
+
if (status.renderedFrames > lastRendered ||
|
|
192
|
+
status.encodedFrames > lastEncoded) {
|
|
193
|
+
lastRendered = Math.max(lastRendered, status.renderedFrames);
|
|
194
|
+
lastEncoded = Math.max(lastEncoded, status.encodedFrames);
|
|
195
|
+
overallProgress.setFrames({
|
|
196
|
+
index: payload.chunk,
|
|
197
|
+
rendered: lastRendered,
|
|
198
|
+
encoded: lastEncoded,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (status.state === 'failed') {
|
|
202
|
+
overallProgress.addErrorWithoutUpload(status.errorInfo);
|
|
203
|
+
overallProgress.setFrames({
|
|
204
|
+
encoded: 0,
|
|
205
|
+
index: payload.chunk,
|
|
206
|
+
rendered: 0,
|
|
207
|
+
});
|
|
208
|
+
await providerSpecifics
|
|
209
|
+
.deleteFile({
|
|
210
|
+
bucketName: payload.bucketName,
|
|
211
|
+
key: statusKey,
|
|
212
|
+
region,
|
|
213
|
+
customCredentials: null,
|
|
214
|
+
forcePathStyle: payload.forcePathStyle,
|
|
215
|
+
requestHandler,
|
|
216
|
+
})
|
|
217
|
+
.catch(() => undefined);
|
|
218
|
+
return {
|
|
219
|
+
type: 'error',
|
|
220
|
+
error: status.errorInfo.stack,
|
|
221
|
+
shouldRetry: status.shouldRetry,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
if (status.state === 'completed') {
|
|
225
|
+
const downloadedKeys = [];
|
|
226
|
+
const emittedArtifacts = [];
|
|
227
|
+
for (const artifact of status.artifacts) {
|
|
228
|
+
let artifactStream;
|
|
229
|
+
try {
|
|
230
|
+
artifactStream = await providerSpecifics.readFile({
|
|
231
|
+
bucketName: payload.bucketName,
|
|
232
|
+
key: artifact.key,
|
|
233
|
+
region,
|
|
234
|
+
expectedBucketOwner,
|
|
235
|
+
forcePathStyle: payload.forcePathStyle,
|
|
236
|
+
requestHandler,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
if (isMissingObjectError(err)) {
|
|
241
|
+
throw new Error(`Renderer completed manifest references missing artifact object ${artifact.key}`);
|
|
242
|
+
}
|
|
243
|
+
throw err;
|
|
244
|
+
}
|
|
245
|
+
emittedArtifacts.push((0, s3_renderer_output_1.artifactFromS3)({
|
|
246
|
+
metadata: artifact.metadata,
|
|
247
|
+
content: await readStreamAsBytes(artifactStream),
|
|
248
|
+
}));
|
|
249
|
+
downloadedKeys.push(artifact.key);
|
|
250
|
+
}
|
|
251
|
+
const downloadedFiles = [];
|
|
252
|
+
for (const [type, key] of [
|
|
253
|
+
['video', status.videoKey],
|
|
254
|
+
['audio', status.audioKey],
|
|
255
|
+
]) {
|
|
256
|
+
if (!key) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const filename = (0, path_1.join)(outdir, `chunk-${String(payload.chunk).padStart(8, '0')}-${type}`);
|
|
260
|
+
let media;
|
|
261
|
+
try {
|
|
262
|
+
media = await providerSpecifics.readFile({
|
|
263
|
+
bucketName: payload.bucketName,
|
|
264
|
+
key,
|
|
265
|
+
region,
|
|
266
|
+
expectedBucketOwner,
|
|
267
|
+
forcePathStyle: payload.forcePathStyle,
|
|
268
|
+
requestHandler,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
if (isMissingObjectError(err)) {
|
|
273
|
+
throw new Error(`Renderer completed manifest references missing media object ${key}`);
|
|
274
|
+
}
|
|
275
|
+
throw err;
|
|
276
|
+
}
|
|
277
|
+
await (0, promises_1.pipeline)(media, (0, fs_1.createWriteStream)(filename));
|
|
278
|
+
downloadedFiles.push(filename);
|
|
279
|
+
downloadedKeys.push(key);
|
|
280
|
+
}
|
|
281
|
+
for (const emittedArtifact of emittedArtifacts) {
|
|
282
|
+
const { alreadyExisted } = onArtifact(emittedArtifact);
|
|
283
|
+
if (alreadyExisted) {
|
|
284
|
+
return {
|
|
285
|
+
type: 'error',
|
|
286
|
+
error: `Chunk ${payload.chunk} emitted an asset filename ${emittedArtifact.filename} at frame ${emittedArtifact.frame} but there is already another artifact with the same name. https://remotion.dev/docs/artifacts`,
|
|
287
|
+
shouldRetry: false,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
files.push(...downloadedFiles);
|
|
292
|
+
overallProgress.addChunkCompleted(payload.chunk, status.startedAt, status.completedAt);
|
|
293
|
+
for (const key of [...downloadedKeys, statusKey]) {
|
|
294
|
+
await providerSpecifics
|
|
295
|
+
.deleteFile({
|
|
296
|
+
bucketName: payload.bucketName,
|
|
297
|
+
key,
|
|
298
|
+
region,
|
|
299
|
+
customCredentials: null,
|
|
300
|
+
forcePathStyle: payload.forcePathStyle,
|
|
301
|
+
requestHandler,
|
|
302
|
+
})
|
|
303
|
+
.catch((err) => {
|
|
304
|
+
renderer_1.RenderInternals.Log.warn({ indent: false, logLevel }, `Could not clean up renderer transport object ${key}`, err);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return { type: 'success' };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
if (!isMissingObjectError(err) &&
|
|
312
|
+
!providerSpecifics.isFlakyError(err)) {
|
|
313
|
+
return {
|
|
314
|
+
type: 'error',
|
|
315
|
+
error: (_b = err.stack) !== null && _b !== void 0 ? _b : err.message,
|
|
316
|
+
shouldRetry: false,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
await wait(pollingInterval);
|
|
321
|
+
pollingInterval = Math.min(2000, Math.round(pollingInterval * 1.5));
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
type: 'error',
|
|
325
|
+
error: `Renderer chunk ${payload.chunk}, attempt ${payload.attempt} did not publish a status before the launcher deadline. Logs: ${providerSpecifics.getLoggingUrlForRendererFunction({
|
|
326
|
+
region,
|
|
327
|
+
functionName,
|
|
328
|
+
rendererFunctionName: functionName,
|
|
329
|
+
renderId: payload.renderId,
|
|
330
|
+
chunk: payload.chunk,
|
|
331
|
+
})}`,
|
|
332
|
+
shouldRetry: false,
|
|
333
|
+
};
|
|
334
|
+
};
|
|
335
|
+
const renderRendererFunctionWithRetry = async ({ payload, files, functionName, outdir, overallProgress, logLevel, onArtifact, providerSpecifics, insideFunctionSpecifics, requestHandler, expectedBucketOwner, }) => {
|
|
114
336
|
if (payload.type !== serverless_client_1.ServerlessRoutines.renderer) {
|
|
115
337
|
throw new Error('Expected renderer type');
|
|
116
338
|
}
|
|
117
|
-
const
|
|
339
|
+
const transport = providerSpecifics.getRendererFunctionTransport(insideFunctionSpecifics.getCurrentRegionInFunction());
|
|
340
|
+
const result = await (transport === 's3' ? s3Renderer : streamRenderer)({
|
|
118
341
|
files,
|
|
119
342
|
functionName,
|
|
120
343
|
outdir,
|
|
@@ -125,9 +348,10 @@ const streamRendererFunctionWithRetry = async ({ payload, files, functionName, o
|
|
|
125
348
|
providerSpecifics,
|
|
126
349
|
insideFunctionSpecifics,
|
|
127
350
|
requestHandler,
|
|
351
|
+
expectedBucketOwner,
|
|
128
352
|
});
|
|
129
353
|
if (result.type === 'error') {
|
|
130
|
-
if (!result.shouldRetry) {
|
|
354
|
+
if (!result.shouldRetry || payload.retriesLeft <= 0) {
|
|
131
355
|
throw new Error(result.error);
|
|
132
356
|
}
|
|
133
357
|
overallProgress.addRetry({
|
|
@@ -135,7 +359,7 @@ const streamRendererFunctionWithRetry = async ({ payload, files, functionName, o
|
|
|
135
359
|
time: Date.now(),
|
|
136
360
|
chunk: payload.chunk,
|
|
137
361
|
});
|
|
138
|
-
return (0, exports.
|
|
362
|
+
return (0, exports.renderRendererFunctionWithRetry)({
|
|
139
363
|
files,
|
|
140
364
|
functionName,
|
|
141
365
|
outdir,
|
|
@@ -150,7 +374,8 @@ const streamRendererFunctionWithRetry = async ({ payload, files, functionName, o
|
|
|
150
374
|
providerSpecifics,
|
|
151
375
|
insideFunctionSpecifics,
|
|
152
376
|
requestHandler,
|
|
377
|
+
expectedBucketOwner,
|
|
153
378
|
});
|
|
154
379
|
}
|
|
155
380
|
};
|
|
156
|
-
exports.
|
|
381
|
+
exports.renderRendererFunctionWithRetry = renderRendererFunctionWithRetry;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"url": "https://github.com/remotion-dev/remotion/tree/main/packages/serverless"
|
|
4
4
|
},
|
|
5
5
|
"name": "@remotion/serverless",
|
|
6
|
-
"version": "4.0.
|
|
6
|
+
"version": "4.0.510",
|
|
7
7
|
"description": "A runtime for distributed rendering",
|
|
8
8
|
"main": "dist",
|
|
9
9
|
"scripts": {
|
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@remotion/renderer": "4.0.
|
|
27
|
-
"@remotion/bundler": "4.0.
|
|
28
|
-
"@remotion/licensing": "4.0.
|
|
29
|
-
"@remotion/serverless-client": "4.0.
|
|
26
|
+
"@remotion/renderer": "4.0.510",
|
|
27
|
+
"@remotion/bundler": "4.0.510",
|
|
28
|
+
"@remotion/licensing": "4.0.510",
|
|
29
|
+
"@remotion/serverless-client": "4.0.510"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@remotion/eslint-config-internal": "4.0.
|
|
32
|
+
"@remotion/eslint-config-internal": "4.0.510",
|
|
33
33
|
"eslint": "9.19.0",
|
|
34
34
|
"@typescript/native-preview": "7.0.0-dev.20260217.1"
|
|
35
35
|
},
|