@remotion/serverless 4.0.509 → 4.0.511

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.
@@ -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: 'USD',
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: `$${new Intl.NumberFormat('en-US', {
37
- currency: 'USD',
37
+ estimatedDisplayCost: `${billingCurrency === 'CNY' ? '¥' : '$'}${new Intl.NumberFormat('en-US', {
38
+ currency: billingCurrency,
38
39
  currencyDisplay: 'narrowSymbol',
39
40
  }).format(cost)}`,
40
41
  },
@@ -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.streamRendererFunctionWithRetry)({
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
- if (audioOutputLocation) {
251
- const audioChunkTimer = insideFunctionSpecifics.timer('Sending audio chunk', params.logLevel);
252
- await onStream({
253
- type: 'audio-chunk-rendered',
254
- payload: new Uint8Array(node_fs_1.default.readFileSync(audioOutputLocation)),
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
- if (videoOutputLocation) {
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,
@@ -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,
@@ -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: (payload) => {
194
- const message = (0, serverless_client_1.makeStreamPayload)({
195
- message: payload,
196
- });
197
- const writeProm = responseWriter.write(message);
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 streamRendererFunctionWithRetry: <Provider extends CloudProvider<string, Record<string, unknown>, Record<string, unknown>, string, object>>({ payload, files, functionName, outdir, overallProgress, logLevel, onArtifact, providerSpecifics, insideFunctionSpecifics, requestHandler, }: {
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>;
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.streamRendererFunctionWithRetry = void 0;
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 streamRendererFunctionWithRetry = async ({ payload, files, functionName, outdir, overallProgress, logLevel, onArtifact, providerSpecifics, insideFunctionSpecifics, requestHandler, }) => {
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 result = await streamRenderer({
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.streamRendererFunctionWithRetry)({
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.streamRendererFunctionWithRetry = streamRendererFunctionWithRetry;
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.509",
6
+ "version": "4.0.511",
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.509",
27
- "@remotion/bundler": "4.0.509",
28
- "@remotion/licensing": "4.0.509",
29
- "@remotion/serverless-client": "4.0.509"
26
+ "@remotion/renderer": "4.0.511",
27
+ "@remotion/bundler": "4.0.511",
28
+ "@remotion/licensing": "4.0.511",
29
+ "@remotion/serverless-client": "4.0.511"
30
30
  },
31
31
  "devDependencies": {
32
- "@remotion/eslint-config-internal": "4.0.509",
32
+ "@remotion/eslint-config-internal": "4.0.511",
33
33
  "eslint": "9.19.0",
34
34
  "@typescript/native-preview": "7.0.0-dev.20260217.1"
35
35
  },