@remotion/serverless 4.0.516 → 4.0.518

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.
@@ -0,0 +1,23 @@
1
+ import type { EmittedArtifact } from '@remotion/renderer';
2
+ type ArtifactRegistration = {
3
+ chunk: number;
4
+ frame: number;
5
+ attempt: number;
6
+ filename: string;
7
+ };
8
+ export type ArtifactRegistrationResult = {
9
+ type: 'accepted';
10
+ } | {
11
+ type: 'retry-replay';
12
+ } | {
13
+ type: 'conflict';
14
+ };
15
+ export type OnArtifactFromRenderer = (params: {
16
+ artifact: EmittedArtifact;
17
+ chunk: number;
18
+ attempt: number;
19
+ }) => ArtifactRegistrationResult;
20
+ export declare const makeArtifactRegistry: () => {
21
+ registerArtifact: (artifact: ArtifactRegistration) => ArtifactRegistrationResult;
22
+ };
23
+ export {};
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeArtifactRegistry = void 0;
4
+ const makeArtifactRegistry = () => {
5
+ const artifactsByFilename = new Map();
6
+ const artifactsByAttempt = new Set();
7
+ const registerArtifact = (artifact) => {
8
+ const attemptKey = JSON.stringify([
9
+ artifact.chunk,
10
+ artifact.attempt,
11
+ artifact.filename,
12
+ ]);
13
+ if (artifactsByAttempt.has(attemptKey)) {
14
+ return { type: 'conflict' };
15
+ }
16
+ artifactsByAttempt.add(attemptKey);
17
+ const existingArtifact = artifactsByFilename.get(artifact.filename);
18
+ if (!existingArtifact) {
19
+ artifactsByFilename.set(artifact.filename, artifact);
20
+ return { type: 'accepted' };
21
+ }
22
+ if (existingArtifact.chunk === artifact.chunk &&
23
+ existingArtifact.frame === artifact.frame &&
24
+ artifact.attempt > existingArtifact.attempt) {
25
+ return { type: 'retry-replay' };
26
+ }
27
+ return { type: 'conflict' };
28
+ };
29
+ return { registerArtifact };
30
+ };
31
+ exports.makeArtifactRegistry = makeArtifactRegistry;
@@ -0,0 +1,24 @@
1
+ import type { CloudProvider, CustomCredentials, DownloadBehavior, PostRenderData, Privacy, ProviderSpecifics, RenderMetadata, SerializedInputProps } from '@remotion/serverless-client';
2
+ import type { OverallProgressHelper } from './overall-render-progress';
3
+ import type { InsideFunctionSpecifics } from './provider-implementation';
4
+ export declare const finishRender: <Provider extends CloudProvider<string, Record<string, unknown>, Record<string, unknown>, string, object>>({ expectedBucketOwner, renderBucketName, customCredentials, downloadBehavior, key, privacy, inputProps, serializedResolvedProps, renderMetadata, logLevel, overallProgress, startTime, providerSpecifics, insideFunctionSpecifics, forcePathStyle, storageClass, requestHandler, outputFile, timeToCombine, }: {
5
+ expectedBucketOwner: string;
6
+ renderBucketName: string;
7
+ customCredentials: CustomCredentials<Provider> | null;
8
+ downloadBehavior: DownloadBehavior;
9
+ key: string;
10
+ privacy: Privacy;
11
+ inputProps: SerializedInputProps;
12
+ serializedResolvedProps: SerializedInputProps;
13
+ renderMetadata: RenderMetadata<Provider>;
14
+ logLevel: "error" | "info" | "trace" | "verbose" | "warn";
15
+ overallProgress: OverallProgressHelper<Provider>;
16
+ startTime: number;
17
+ providerSpecifics: ProviderSpecifics<Provider>;
18
+ insideFunctionSpecifics: InsideFunctionSpecifics<Provider>;
19
+ forcePathStyle: boolean;
20
+ storageClass: Provider["storageClass"] | null;
21
+ requestHandler: Provider["requestHandler"] | null;
22
+ outputFile: string;
23
+ timeToCombine: number | null;
24
+ }) => Promise<PostRenderData<Provider>>;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.finishRender = void 0;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const serverless_client_1 = require("@remotion/serverless-client");
9
+ const cleanup_props_1 = require("./cleanup-props");
10
+ const create_post_render_data_1 = require("./create-post-render-data");
11
+ const finishRender = async ({ expectedBucketOwner, renderBucketName, customCredentials, downloadBehavior, key, privacy, inputProps, serializedResolvedProps, renderMetadata, logLevel, overallProgress, startTime, providerSpecifics, insideFunctionSpecifics, forcePathStyle, storageClass, requestHandler, outputFile, timeToCombine, }) => {
12
+ const outputSize = node_fs_1.default.statSync(outputFile).size;
13
+ const writeToBucket = insideFunctionSpecifics.timer(`Writing to bucket (${outputSize} bytes)`, logLevel);
14
+ await providerSpecifics.writeFile({
15
+ bucketName: renderBucketName,
16
+ key,
17
+ body: node_fs_1.default.createReadStream(outputFile),
18
+ region: insideFunctionSpecifics.getCurrentRegionInFunction(),
19
+ privacy,
20
+ expectedBucketOwner,
21
+ downloadBehavior,
22
+ customCredentials,
23
+ forcePathStyle,
24
+ storageClass,
25
+ requestHandler,
26
+ });
27
+ writeToBucket.end();
28
+ const errorExplanations = (0, serverless_client_1.inspectErrors)({
29
+ errors: overallProgress.get().errors,
30
+ });
31
+ const cleanupProm = (0, cleanup_props_1.cleanupProps)({
32
+ inputProps,
33
+ serializedResolvedProps,
34
+ providerSpecifics,
35
+ forcePathStyle,
36
+ insideFunctionSpecifics,
37
+ });
38
+ const { url: outputUrl } = providerSpecifics.getOutputUrl({
39
+ bucketName: renderBucketName,
40
+ currentRegion: insideFunctionSpecifics.getCurrentRegionInFunction(),
41
+ customCredentials,
42
+ renderMetadata,
43
+ });
44
+ const postRenderData = (0, create_post_render_data_1.createPostRenderData)({
45
+ region: insideFunctionSpecifics.getCurrentRegionInFunction(),
46
+ memorySizeInMb: insideFunctionSpecifics.getCurrentMemorySizeInMb(),
47
+ renderMetadata,
48
+ errorExplanations,
49
+ timeToDelete: (await cleanupProm).reduce((a, b) => Math.max(a, b), 0),
50
+ outputFile: {
51
+ sizeInBytes: outputSize,
52
+ url: outputUrl,
53
+ },
54
+ outputSize,
55
+ timeToCombine,
56
+ overallProgress: overallProgress.get(),
57
+ timeToFinish: Date.now() - startTime,
58
+ providerSpecifics,
59
+ });
60
+ await overallProgress.setPostRenderData(postRenderData);
61
+ node_fs_1.default.unlinkSync(outputFile);
62
+ return postRenderData;
63
+ };
64
+ exports.finishRender = finishRender;
@@ -1,8 +1,10 @@
1
1
  import type { CloudProvider, ProviderSpecifics, ServerlessPayload } from '@remotion/serverless-client';
2
2
  import type { InsideFunctionSpecifics } from '../provider-implementation';
3
+ import type { RequestContext } from './renderer';
3
4
  type Options = {
4
5
  expectedBucketOwner: string;
5
6
  getRemainingTimeInMillis: () => number;
7
+ requestContext: RequestContext | null;
6
8
  };
7
9
  export declare const launchHandler: <Provider extends CloudProvider<string, Record<string, unknown>, Record<string, unknown>, string, object>>({ params, options, providerSpecifics, insideFunctionSpecifics, }: {
8
10
  params: ServerlessPayload<Provider>;
@@ -6,13 +6,16 @@ const path_1 = require("path");
6
6
  const renderer_1 = require("@remotion/renderer");
7
7
  const serverless_client_1 = require("@remotion/serverless-client");
8
8
  const serverless_client_2 = require("@remotion/serverless-client");
9
+ const artifact_registry_1 = require("../artifact-registry");
9
10
  const cleanup_props_1 = require("../cleanup-props");
10
11
  const find_output_file_in_bucket_1 = require("../find-output-file-in-bucket");
12
+ const finish_render_1 = require("../finish-render");
11
13
  const get_tmp_dir_1 = require("../get-tmp-dir");
12
14
  const merge_chunks_1 = require("../merge-chunks");
13
15
  const overall_render_progress_1 = require("../overall-render-progress");
14
16
  const plan_frame_ranges_1 = require("../plan-frame-ranges");
15
17
  const remove_outname_credentials_1 = require("../remove-outname-credentials");
18
+ const render_with_single_function_1 = require("../render-with-single-function");
16
19
  const stream_renderer_1 = require("../stream-renderer");
17
20
  const validate_composition_1 = require("../validate-composition");
18
21
  const send_telemetry_event_1 = require("./send-telemetry-event");
@@ -223,16 +226,18 @@ const innerLaunchHandler = async ({ params, options, overallProgress, registerCl
223
226
  });
224
227
  renderer_1.RenderInternals.Log.info(logOptions, 'Render plan: ', chunks.map((c, i) => `Chunk ${i} (Frames ${c[0]} - ${c[1]})`).join(', '));
225
228
  const rendererFunctionName = (_j = params.rendererFunctionName) !== null && _j !== void 0 ? _j : insideFunctionSpecifics.getCurrentFunctionName();
229
+ const shouldRenderDirectly = params.concurrency === 1 &&
230
+ params.rendererFunctionName === null &&
231
+ options.requestContext !== null;
226
232
  const renderMetadata = {
227
233
  startedDate,
228
234
  totalChunks: chunks.length,
229
235
  estimatedTotalLambdaInvokations: [
230
- // Direct invokations
231
- chunks.length,
236
+ shouldRenderDirectly ? 0 : chunks.length,
232
237
  // This function
233
238
  1,
234
239
  ].reduce((a, b) => a + b, 0),
235
- estimatedRenderLambdaInvokations: chunks.length,
240
+ estimatedRenderLambdaInvokations: shouldRenderDirectly ? 0 : chunks.length,
236
241
  compositionId: comp.id,
237
242
  siteId: params.serveUrl,
238
243
  codec: params.codec,
@@ -290,20 +295,17 @@ const innerLaunchHandler = async ({ params, options, overallProgress, registerCl
290
295
  findOutputFile.end();
291
296
  }
292
297
  overallProgress.setRenderMetadata(renderMetadata);
293
- const outdir = (0, path_1.join)(renderer_1.RenderInternals.tmpDir(serverless_client_2.CONCAT_FOLDER_TOKEN), 'bucket');
294
- if ((0, fs_1.existsSync)(outdir)) {
295
- (0, fs_1.rmSync)(outdir, {
296
- recursive: true,
297
- });
298
- }
299
- (0, fs_1.mkdirSync)(outdir);
300
- const files = [];
301
- const onArtifact = (artifact) => {
298
+ const artifactRegistry = (0, artifact_registry_1.makeArtifactRegistry)();
299
+ const onArtifact = ({ artifact, chunk, attempt }) => {
302
300
  var _a;
303
- if (overallProgress
304
- .getReceivedArtifacts()
305
- .find((a) => a.filename === artifact.filename)) {
306
- return { alreadyExisted: true };
301
+ const artifactRegistration = artifactRegistry.registerArtifact({
302
+ chunk,
303
+ frame: artifact.frame,
304
+ attempt,
305
+ filename: artifact.filename,
306
+ });
307
+ if (artifactRegistration.type !== 'accepted') {
308
+ return artifactRegistration;
307
309
  }
308
310
  const region = insideFunctionSpecifics.getCurrentRegionInFunction();
309
311
  const storageKey = (0, serverless_client_2.artifactName)(renderMetadata.renderId, artifact.filename);
@@ -340,70 +342,122 @@ const innerLaunchHandler = async ({ params, options, overallProgress, registerCl
340
342
  stack: err.stack,
341
343
  tmpDir: null,
342
344
  frame: artifact.frame,
343
- chunk: null,
345
+ chunk,
344
346
  isFatal: false,
345
- attempt: 1,
347
+ attempt,
346
348
  willRetry: false,
347
349
  totalAttempts: 1,
348
350
  });
349
351
  overallProgress.upload('artifactWriteError');
350
352
  renderer_1.RenderInternals.Log.error({ indent: false, logLevel: params.logLevel }, 'Failed to write artifact to S3', err);
351
353
  });
352
- return { alreadyExisted: false };
354
+ return artifactRegistration;
353
355
  };
354
- await Promise.all(lambdaPayloads.map(async (payload) => {
355
- await (0, stream_renderer_1.renderRendererFunctionWithRetry)({
356
- files,
357
- functionName: rendererFunctionName,
358
- outdir,
356
+ let postRenderData;
357
+ if (shouldRenderDirectly) {
358
+ const directRender = await (0, render_with_single_function_1.renderWithSingleFunction)({
359
+ params,
360
+ composition: comp,
361
+ serializedInputPropsWithCustomSchema,
362
+ frameRange: realFrameRange,
363
+ browserInstance: instance,
364
+ chromiumOptions: chromiumParams,
359
365
  overallProgress,
360
- payload,
361
- logLevel: params.logLevel,
362
366
  onArtifact,
363
367
  providerSpecifics,
364
368
  insideFunctionSpecifics,
365
- requestHandler: null,
369
+ });
370
+ try {
371
+ postRenderData = await (0, finish_render_1.finishRender)({
372
+ expectedBucketOwner: options.expectedBucketOwner,
373
+ renderBucketName,
374
+ customCredentials,
375
+ downloadBehavior: params.downloadBehavior,
376
+ key,
377
+ privacy: params.privacy,
378
+ inputProps: params.inputProps,
379
+ serializedResolvedProps,
380
+ renderMetadata,
381
+ logLevel: params.logLevel,
382
+ overallProgress,
383
+ startTime,
384
+ providerSpecifics,
385
+ insideFunctionSpecifics,
386
+ forcePathStyle: params.forcePathStyle,
387
+ storageClass: params.storageClass,
388
+ requestHandler: null,
389
+ outputFile: directRender.outputFile,
390
+ timeToCombine: null,
391
+ });
392
+ }
393
+ finally {
394
+ await directRender.cleanup();
395
+ }
396
+ }
397
+ else {
398
+ const outdir = (0, path_1.join)(renderer_1.RenderInternals.tmpDir(serverless_client_2.CONCAT_FOLDER_TOKEN), 'bucket');
399
+ if ((0, fs_1.existsSync)(outdir)) {
400
+ (0, fs_1.rmSync)(outdir, {
401
+ recursive: true,
402
+ });
403
+ }
404
+ (0, fs_1.mkdirSync)(outdir);
405
+ const files = [];
406
+ await Promise.all(lambdaPayloads.map(async (payload) => {
407
+ await (0, stream_renderer_1.renderRendererFunctionWithRetry)({
408
+ files,
409
+ functionName: rendererFunctionName,
410
+ outdir,
411
+ overallProgress,
412
+ payload,
413
+ logLevel: params.logLevel,
414
+ onArtifact,
415
+ providerSpecifics,
416
+ insideFunctionSpecifics,
417
+ requestHandler: null,
418
+ expectedBucketOwner: options.expectedBucketOwner,
419
+ });
420
+ }));
421
+ postRenderData = await (0, merge_chunks_1.mergeChunksAndFinishRender)({
422
+ bucketName: params.bucketName,
423
+ renderId: params.renderId,
366
424
  expectedBucketOwner: options.expectedBucketOwner,
425
+ numberOfFrames: comp.durationInFrames,
426
+ audioCodec: params.audioCodec,
427
+ chunkCount: chunks.length,
428
+ codec: params.codec,
429
+ customCredentials,
430
+ downloadBehavior: params.downloadBehavior,
431
+ fps,
432
+ key,
433
+ numberOfGifLoops: params.numberOfGifLoops,
434
+ privacy: params.privacy,
435
+ renderBucketName,
436
+ inputProps: params.inputProps,
437
+ serializedResolvedProps,
438
+ renderMetadata,
439
+ audioBitrate: params.audioBitrate,
440
+ logLevel: params.logLevel,
441
+ framesPerLambda,
442
+ binariesDirectory: null,
443
+ preferLossless: params.preferLossless,
444
+ compositionStart: realFrameRange[0],
445
+ outdir,
446
+ files: files.sort(),
447
+ overallProgress,
448
+ startTime,
449
+ providerSpecifics,
450
+ forcePathStyle: params.forcePathStyle,
451
+ insideFunctionSpecifics,
452
+ everyNthFrame: params.everyNthFrame,
453
+ frameRange: params.frameRange,
454
+ storageClass: params.storageClass,
455
+ requestHandler: null,
456
+ sampleRate: params.sampleRate,
367
457
  });
368
- }));
369
- const postRenderData = await (0, merge_chunks_1.mergeChunksAndFinishRender)({
370
- bucketName: params.bucketName,
371
- renderId: params.renderId,
372
- expectedBucketOwner: options.expectedBucketOwner,
373
- numberOfFrames: comp.durationInFrames,
374
- audioCodec: params.audioCodec,
375
- chunkCount: chunks.length,
376
- codec: params.codec,
377
- customCredentials,
378
- downloadBehavior: params.downloadBehavior,
379
- fps,
380
- key,
381
- numberOfGifLoops: params.numberOfGifLoops,
382
- privacy: params.privacy,
383
- renderBucketName,
384
- inputProps: params.inputProps,
385
- serializedResolvedProps,
386
- renderMetadata,
387
- audioBitrate: params.audioBitrate,
388
- logLevel: params.logLevel,
389
- framesPerLambda,
390
- binariesDirectory: null,
391
- preferLossless: params.preferLossless,
392
- compositionStart: realFrameRange[0],
393
- outdir,
394
- files: files.sort(),
395
- overallProgress,
396
- startTime,
397
- providerSpecifics,
398
- forcePathStyle: params.forcePathStyle,
399
- insideFunctionSpecifics,
400
- everyNthFrame: params.everyNthFrame,
401
- frameRange: params.frameRange,
402
- storageClass: params.storageClass,
403
- requestHandler: null,
404
- sampleRate: params.sampleRate,
405
- });
406
- if (providerSpecifics.getRendererFunctionTransport(insideFunctionSpecifics.getCurrentRegionInFunction()) === 's3') {
458
+ }
459
+ if (!shouldRenderDirectly &&
460
+ providerSpecifics.getRendererFunctionTransport(insideFunctionSpecifics.getCurrentRegionInFunction()) === 's3') {
407
461
  try {
408
462
  const transportObjects = await providerSpecifics.listObjects({
409
463
  bucketName: params.bucketName,
@@ -9,7 +9,7 @@ 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, onMediaFiles, }: {
12
+ export declare const rendererHandler: <Provider extends CloudProvider<string, Record<string, unknown>, Record<string, unknown>, string, object>>({ onStream, options, params, providerSpecifics, requestContext, insideFunctionSpecifics, onMediaFiles, executionMode, }: {
13
13
  params: ServerlessPayload<Provider>;
14
14
  options: Options;
15
15
  onStream: OnStream<Provider>;
@@ -22,5 +22,6 @@ export declare const rendererHandler: <Provider extends CloudProvider<string, Re
22
22
  isAudioOnly: boolean;
23
23
  completedAt: number;
24
24
  }) => Promise<void>) | null;
25
+ executionMode: "direct" | "invoked";
25
26
  }) => Promise<void>;
26
27
  export {};
@@ -285,9 +285,9 @@ const renderHandler = async ({ params, options, logs, onStream, providerSpecific
285
285
  streamTimer.end();
286
286
  renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel: params.logLevel }, 'Cleaning up and writing timings');
287
287
  await Promise.all([
288
- node_fs_1.default.promises.rm(videoOutputLocation, { recursive: true }),
288
+ node_fs_1.default.promises.rm(videoOutputLocation, { recursive: true, force: true }),
289
289
  audioOutputLocation
290
- ? node_fs_1.default.promises.rm(audioOutputLocation, { recursive: true })
290
+ ? node_fs_1.default.promises.rm(audioOutputLocation, { recursive: true, force: true })
291
291
  : null,
292
292
  node_fs_1.default.promises.rm(outputPath, { recursive: true }),
293
293
  ].filter(serverless_client_1.truthy));
@@ -295,7 +295,7 @@ const renderHandler = async ({ params, options, logs, onStream, providerSpecific
295
295
  return {};
296
296
  };
297
297
  const ENABLE_SLOW_LEAK_DETECTION = false;
298
- const rendererHandler = async ({ onStream, options, params, providerSpecifics, requestContext, insideFunctionSpecifics, onMediaFiles, }) => {
298
+ const rendererHandler = async ({ onStream, options, params, providerSpecifics, requestContext, insideFunctionSpecifics, onMediaFiles, executionMode, }) => {
299
299
  if (params.type !== serverless_client_1.ServerlessRoutines.renderer) {
300
300
  throw new Error('Params must be renderer');
301
301
  }
@@ -374,7 +374,12 @@ const rendererHandler = async ({ onStream, options, params, providerSpecifics, r
374
374
  }
375
375
  finally {
376
376
  stopCancellationPolling();
377
- if (shouldKeepBrowserOpen && instance) {
377
+ if (executionMode === 'direct') {
378
+ if (!shouldKeepBrowserOpen && instance) {
379
+ await instance.instance.close({ silent: true });
380
+ }
381
+ }
382
+ else if (shouldKeepBrowserOpen && instance) {
378
383
  insideFunctionSpecifics.forgetBrowserEventLoop({
379
384
  logLevel: params.logLevel,
380
385
  launchedBrowser: instance,
@@ -159,6 +159,7 @@ const innerHandler = async ({ params, responseWriter, context, providerSpecifics
159
159
  options: {
160
160
  expectedBucketOwner: currentUserId,
161
161
  getRemainingTimeInMillis: context.getRemainingTimeInMillis,
162
+ requestContext: context,
162
163
  },
163
164
  providerSpecifics,
164
165
  insideFunctionSpecifics,
@@ -236,6 +237,7 @@ const innerHandler = async ({ params, responseWriter, context, providerSpecifics
236
237
  });
237
238
  },
238
239
  onMediaFiles: (_a = s3Output === null || s3Output === void 0 ? void 0 : s3Output.uploadMediaAndComplete) !== null && _a !== void 0 ? _a : null,
240
+ executionMode: 'invoked',
239
241
  requestContext: context,
240
242
  providerSpecifics,
241
243
  insideFunctionSpecifics,
@@ -1,14 +1,8 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.mergeChunksAndFinishRender = void 0;
7
- const fs_1 = __importDefault(require("fs"));
8
- const serverless_client_1 = require("@remotion/serverless-client");
9
- const cleanup_props_1 = require("./cleanup-props");
10
4
  const concat_videos_1 = require("./concat-videos");
11
- const create_post_render_data_1 = require("./create-post-render-data");
5
+ const finish_render_1 = require("./finish-render");
12
6
  const mergeChunksAndFinishRender = async (options) => {
13
7
  const onProgress = ({ frames: framesEncoded }) => {
14
8
  options.overallProgress.setCombinedFrames(framesEncoded);
@@ -43,56 +37,27 @@ const mergeChunksAndFinishRender = async (options) => {
43
37
  });
44
38
  const encodingStop = Date.now();
45
39
  options.overallProgress.setTimeToCombine(encodingStop - encodingStart);
46
- const outputSize = fs_1.default.statSync(outfile).size;
47
- const writeToBucket = options.insideFunctionSpecifics.timer(`Writing to bucket (${outputSize} bytes)`, options.logLevel);
48
- await options.providerSpecifics.writeFile({
49
- bucketName: options.renderBucketName,
50
- key: options.key,
51
- body: fs_1.default.createReadStream(outfile),
52
- region: options.insideFunctionSpecifics.getCurrentRegionInFunction(),
53
- privacy: options.privacy,
40
+ const postRenderData = await (0, finish_render_1.finishRender)({
54
41
  expectedBucketOwner: options.expectedBucketOwner,
55
- downloadBehavior: options.downloadBehavior,
42
+ renderBucketName: options.renderBucketName,
56
43
  customCredentials: options.customCredentials,
57
- forcePathStyle: options.forcePathStyle,
58
- storageClass: options.storageClass,
59
- requestHandler: options.requestHandler,
60
- });
61
- writeToBucket.end();
62
- const errorExplanations = (0, serverless_client_1.inspectErrors)({
63
- errors: options.overallProgress.get().errors,
64
- });
65
- const cleanupProm = (0, cleanup_props_1.cleanupProps)({
44
+ downloadBehavior: options.downloadBehavior,
45
+ key: options.key,
46
+ privacy: options.privacy,
66
47
  inputProps: options.inputProps,
67
48
  serializedResolvedProps: options.serializedResolvedProps,
49
+ renderMetadata: options.renderMetadata,
50
+ logLevel: options.logLevel,
51
+ overallProgress: options.overallProgress,
52
+ startTime: options.startTime,
68
53
  providerSpecifics: options.providerSpecifics,
69
- forcePathStyle: options.forcePathStyle,
70
54
  insideFunctionSpecifics: options.insideFunctionSpecifics,
71
- });
72
- const { url: outputUrl } = options.providerSpecifics.getOutputUrl({
73
- bucketName: options.renderBucketName,
74
- currentRegion: options.insideFunctionSpecifics.getCurrentRegionInFunction(),
75
- customCredentials: options.customCredentials,
76
- renderMetadata: options.renderMetadata,
77
- });
78
- const postRenderData = (0, create_post_render_data_1.createPostRenderData)({
79
- region: options.insideFunctionSpecifics.getCurrentRegionInFunction(),
80
- memorySizeInMb: options.insideFunctionSpecifics.getCurrentMemorySizeInMb(),
81
- renderMetadata: options.renderMetadata,
82
- errorExplanations,
83
- timeToDelete: (await cleanupProm).reduce((a, b) => Math.max(a, b), 0),
84
- outputFile: {
85
- sizeInBytes: outputSize,
86
- url: outputUrl,
87
- },
88
- outputSize,
55
+ forcePathStyle: options.forcePathStyle,
56
+ storageClass: options.storageClass,
57
+ requestHandler: options.requestHandler,
58
+ outputFile: outfile,
89
59
  timeToCombine: encodingStop - encodingStart,
90
- overallProgress: options.overallProgress.get(),
91
- timeToFinish: Date.now() - options.startTime,
92
- providerSpecifics: options.providerSpecifics,
93
60
  });
94
- await options.overallProgress.setPostRenderData(postRenderData);
95
- fs_1.default.unlinkSync(outfile);
96
61
  await cleanupChunksProm;
97
62
  return postRenderData;
98
63
  };
@@ -0,0 +1,20 @@
1
+ import type { ChromiumOptions, ResolvedFrameRange } from '@remotion/renderer';
2
+ import type { CloudProvider, ProviderSpecifics, ServerlessPayload, VideoConfig } from '@remotion/serverless-client';
3
+ import type { OnArtifactFromRenderer } from './artifact-registry';
4
+ import type { OverallProgressHelper } from './overall-render-progress';
5
+ import type { InsideFunctionSpecifics } from './provider-implementation';
6
+ export declare const renderWithSingleFunction: <Provider extends CloudProvider<string, Record<string, unknown>, Record<string, unknown>, string, object>>({ params, composition, serializedInputPropsWithCustomSchema, frameRange, browserInstance, chromiumOptions, overallProgress, onArtifact, providerSpecifics, insideFunctionSpecifics, }: {
7
+ params: ServerlessPayload<Provider>;
8
+ composition: VideoConfig;
9
+ serializedInputPropsWithCustomSchema: string;
10
+ frameRange: ResolvedFrameRange;
11
+ browserInstance: import("@remotion/renderer").HeadlessBrowser;
12
+ chromiumOptions: ChromiumOptions;
13
+ overallProgress: OverallProgressHelper<Provider>;
14
+ onArtifact: OnArtifactFromRenderer;
15
+ providerSpecifics: ProviderSpecifics<Provider>;
16
+ insideFunctionSpecifics: InsideFunctionSpecifics<Provider>;
17
+ }) => Promise<{
18
+ outputFile: string;
19
+ cleanup: () => Promise<void>;
20
+ }>;
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.renderWithSingleFunction = void 0;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = require("node:path");
9
+ const renderer_1 = require("@remotion/renderer");
10
+ const serverless_client_1 = require("@remotion/serverless-client");
11
+ const cancellation_polling_1 = require("./cancellation-polling");
12
+ const on_downloads_helpers_1 = require("./on-downloads-helpers");
13
+ const renderWithSingleFunction = async ({ params, composition, serializedInputPropsWithCustomSchema, frameRange, browserInstance, chromiumOptions, overallProgress, onArtifact, providerSpecifics, insideFunctionSpecifics, }) => {
14
+ var _a, _b, _c, _d, _e, _f;
15
+ if (params.type !== serverless_client_1.ServerlessRoutines.launch) {
16
+ throw new Error('Expected launch type');
17
+ }
18
+ const outputDirectory = renderer_1.RenderInternals.tmpDir('remotion-direct-render-');
19
+ const outputFile = (0, node_path_1.join)(outputDirectory, `output.${renderer_1.RenderInternals.getFileExtensionFromCodec(params.codec, params.audioCodec)}`);
20
+ const cleanup = () => node_fs_1.default.promises.rm(outputDirectory, { recursive: true, force: true });
21
+ const frameCount = renderer_1.RenderInternals.getFramesToRender(frameRange, params.everyNthFrame).length;
22
+ const startedAt = Date.now();
23
+ const { cancel, cancelSignal } = (0, renderer_1.makeCancelSignal)();
24
+ const stopCancellationPolling = params.enableCancellation
25
+ ? (0, cancellation_polling_1.startCancellationPolling)({
26
+ bucketName: params.bucketName,
27
+ renderId: params.renderId,
28
+ region: insideFunctionSpecifics.getCurrentRegionInFunction(),
29
+ providerSpecifics,
30
+ forcePathStyle: params.forcePathStyle,
31
+ intervalInMilliseconds: 1000,
32
+ logLevel: params.logLevel,
33
+ onCancelled: cancel,
34
+ })
35
+ : () => undefined;
36
+ try {
37
+ await (0, renderer_1.renderMedia)({
38
+ composition,
39
+ serveUrl: params.serveUrl,
40
+ codec: params.codec,
41
+ outputLocation: outputFile,
42
+ inputProps: JSON.parse(serializedInputPropsWithCustomSchema),
43
+ imageFormat: params.imageFormat,
44
+ frameRange,
45
+ concurrency: params.concurrencyPerFunction,
46
+ puppeteerInstance: browserInstance,
47
+ jpegQuality: (_a = params.jpegQuality) !== null && _a !== void 0 ? _a : renderer_1.RenderInternals.DEFAULT_JPEG_QUALITY,
48
+ envVariables: (_b = params.envVariables) !== null && _b !== void 0 ? _b : {},
49
+ logLevel: params.logLevel,
50
+ crf: params.crf,
51
+ pixelFormat: (_c = params.pixelFormat) !== null && _c !== void 0 ? _c : undefined,
52
+ proResProfile: (_d = params.proResProfile) !== null && _d !== void 0 ? _d : undefined,
53
+ x264Preset: params.x264Preset,
54
+ gopSize: (_e = params.gopSize) !== null && _e !== void 0 ? _e : null,
55
+ onDownload: (0, on_downloads_helpers_1.onDownloadsHelper)(params.logLevel),
56
+ overwrite: true,
57
+ chromiumOptions,
58
+ scale: params.scale,
59
+ timeoutInMilliseconds: params.timeoutInMilliseconds,
60
+ everyNthFrame: params.everyNthFrame,
61
+ numberOfGifLoops: params.numberOfGifLoops,
62
+ muted: params.muted,
63
+ enforceAudioTrack: true,
64
+ audioBitrate: params.audioBitrate,
65
+ videoBitrate: params.videoBitrate,
66
+ encodingBufferSize: params.encodingBufferSize,
67
+ encodingMaxRate: params.encodingMaxRate,
68
+ audioCodec: params.audioCodec,
69
+ preferLossless: params.preferLossless,
70
+ browserExecutable: providerSpecifics.getChromiumPath(),
71
+ cancelSignal: params.enableCancellation ? cancelSignal : undefined,
72
+ disallowParallelEncoding: false,
73
+ offthreadVideoCacheSizeInBytes: params.offthreadVideoCacheSizeInBytes,
74
+ colorSpace: (_f = params.colorSpace) !== null && _f !== void 0 ? _f : undefined,
75
+ repro: false,
76
+ binariesDirectory: null,
77
+ onBrowserDownload: () => {
78
+ throw new Error('Should not download a browser in Lambda');
79
+ },
80
+ onArtifact: (artifact) => {
81
+ const registration = onArtifact({
82
+ artifact,
83
+ chunk: 0,
84
+ attempt: 1,
85
+ });
86
+ if (registration.type === 'conflict') {
87
+ throw new Error(`The artifact filename ${artifact.filename} was emitted more than once. https://remotion.dev/docs/artifacts`);
88
+ }
89
+ },
90
+ metadata: params.metadata,
91
+ hardwareAcceleration: 'disable',
92
+ chromeMode: 'headless-shell',
93
+ offthreadVideoThreads: params.offthreadVideoThreads,
94
+ mediaCacheSizeInBytes: params.mediaCacheSizeInBytes,
95
+ licenseKey: null,
96
+ isProduction: false,
97
+ sampleRate: params.sampleRate,
98
+ onProgress: ({ renderedFrames, encodedFrames }) => {
99
+ overallProgress.setFrames({
100
+ index: 0,
101
+ rendered: renderedFrames,
102
+ encoded: encodedFrames,
103
+ });
104
+ },
105
+ });
106
+ overallProgress.setFrames({
107
+ index: 0,
108
+ rendered: frameCount,
109
+ encoded: frameCount,
110
+ });
111
+ overallProgress.addChunkCompleted(0, startedAt, Date.now());
112
+ overallProgress.setCombinedFrames(frameCount);
113
+ return { outputFile, cleanup };
114
+ }
115
+ catch (err) {
116
+ await cleanup();
117
+ throw err;
118
+ }
119
+ finally {
120
+ stopCancellationPolling();
121
+ }
122
+ };
123
+ exports.renderWithSingleFunction = renderWithSingleFunction;
@@ -1,5 +1,5 @@
1
- import type { EmittedArtifact } from '@remotion/renderer';
2
1
  import type { CloudProvider, ProviderSpecifics, ServerlessPayload } from '@remotion/serverless-client';
2
+ import type { OnArtifactFromRenderer } from './artifact-registry';
3
3
  import type { OverallProgressHelper } from './overall-render-progress';
4
4
  import type { InsideFunctionSpecifics } from './provider-implementation';
5
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, }: {
@@ -9,9 +9,7 @@ export declare const renderRendererFunctionWithRetry: <Provider extends CloudPro
9
9
  overallProgress: OverallProgressHelper<Provider>;
10
10
  files: string[];
11
11
  logLevel: "error" | "info" | "trace" | "verbose" | "warn";
12
- onArtifact: (asset: EmittedArtifact) => {
13
- alreadyExisted: boolean;
14
- };
12
+ onArtifact: OnArtifactFromRenderer;
15
13
  providerSpecifics: ProviderSpecifics<Provider>;
16
14
  insideFunctionSpecifics: InsideFunctionSpecifics<Provider>;
17
15
  requestHandler: Provider["requestHandler"] | null;
@@ -76,14 +76,21 @@ const streamRenderer = ({ payload, functionName, outdir, overallProgress, files,
76
76
  if (message.type === 'artifact-emitted') {
77
77
  const artifact = (0, serverless_client_1.deserializeArtifact)(message.payload.artifact);
78
78
  renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `Received artifact on frame ${message.payload.artifact.frame}:`, artifact.filename, artifact.content.length + 'bytes.');
79
- const { alreadyExisted } = onArtifact(artifact);
80
- if (alreadyExisted) {
79
+ const artifactRegistration = onArtifact({
80
+ artifact,
81
+ chunk: payload.chunk,
82
+ attempt: payload.attempt,
83
+ });
84
+ if (artifactRegistration.type === 'conflict') {
81
85
  return resolve({
82
86
  type: 'error',
83
87
  error: `Chunk ${payload.chunk} emitted an asset filename ${message.payload.artifact.filename} at frame ${message.payload.artifact.frame} but there is already another artifact with the same name. https://remotion.dev/docs/artifacts`,
84
88
  shouldRetry: false,
85
89
  });
86
90
  }
91
+ if (artifactRegistration.type === 'retry-replay') {
92
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `Ignoring artifact ${artifact.filename} replayed by attempt ${payload.attempt} of chunk ${payload.chunk}`);
93
+ }
87
94
  return;
88
95
  }
89
96
  if (message.type === 'error-occurred') {
@@ -309,14 +316,21 @@ const s3Renderer = async ({ payload, functionName, outdir, overallProgress, file
309
316
  downloadedKeys.push(key);
310
317
  }
311
318
  for (const emittedArtifact of emittedArtifacts) {
312
- const { alreadyExisted } = onArtifact(emittedArtifact);
313
- if (alreadyExisted) {
319
+ const artifactRegistration = onArtifact({
320
+ artifact: emittedArtifact,
321
+ chunk: payload.chunk,
322
+ attempt: payload.attempt,
323
+ });
324
+ if (artifactRegistration.type === 'conflict') {
314
325
  return {
315
326
  type: 'error',
316
327
  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`,
317
328
  shouldRetry: false,
318
329
  };
319
330
  }
331
+ if (artifactRegistration.type === 'retry-replay') {
332
+ renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `Ignoring artifact ${emittedArtifact.filename} replayed by attempt ${payload.attempt} of chunk ${payload.chunk}`);
333
+ }
320
334
  }
321
335
  files.push(...downloadedFiles);
322
336
  overallProgress.addChunkCompleted(payload.chunk, status.startedAt, status.completedAt);
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.516",
6
+ "version": "4.0.518",
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.516",
27
- "@remotion/bundler": "4.0.516",
28
- "@remotion/licensing": "4.0.516",
29
- "@remotion/serverless-client": "4.0.516"
26
+ "@remotion/renderer": "4.0.518",
27
+ "@remotion/bundler": "4.0.518",
28
+ "@remotion/licensing": "4.0.518",
29
+ "@remotion/serverless-client": "4.0.518"
30
30
  },
31
31
  "devDependencies": {
32
- "@remotion/eslint-config-internal": "4.0.516",
32
+ "@remotion/eslint-config-internal": "4.0.518",
33
33
  "eslint": "9.19.0",
34
34
  "@typescript/native-preview": "7.0.0-dev.20260217.1"
35
35
  },