@remotion/lambda 4.0.122 → 4.0.124

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.
Files changed (36) hide show
  1. package/dist/api/make-lambda-payload.d.ts +1 -1
  2. package/dist/api/make-lambda-payload.js +2 -1
  3. package/dist/api/render-media-on-lambda.js +2 -1
  4. package/dist/cli/commands/render/progress.d.ts +1 -1
  5. package/dist/cli/commands/render/progress.js +4 -4
  6. package/dist/cli/commands/render/render.js +7 -3
  7. package/dist/functions/helpers/can-concat-seamlessly.d.ts +3 -2
  8. package/dist/functions/helpers/can-concat-seamlessly.js +11 -4
  9. package/dist/functions/helpers/concat-videos.d.ts +5 -2
  10. package/dist/functions/helpers/concat-videos.js +22 -7
  11. package/dist/functions/helpers/create-post-render-data.js +7 -1
  12. package/dist/functions/helpers/expected-out-name.js +6 -1
  13. package/dist/functions/helpers/get-cleanup-progress.d.ts +3 -1
  14. package/dist/functions/helpers/get-cleanup-progress.js +3 -1
  15. package/dist/functions/helpers/get-files-to-delete.d.ts +3 -1
  16. package/dist/functions/helpers/get-files-to-delete.js +22 -6
  17. package/dist/functions/helpers/get-progress.js +14 -3
  18. package/dist/functions/helpers/make-timeout-message.js +5 -3
  19. package/dist/functions/helpers/merge-chunks.d.ts +3 -0
  20. package/dist/functions/helpers/merge-chunks.js +15 -1
  21. package/dist/functions/helpers/timer.d.ts +2 -1
  22. package/dist/functions/helpers/timer.js +4 -2
  23. package/dist/functions/launch.js +19 -4
  24. package/dist/functions/merge.js +5 -0
  25. package/dist/functions/renderer.js +66 -26
  26. package/dist/functions/start.js +1 -0
  27. package/dist/functions/still.js +6 -3
  28. package/dist/internals.d.ts +1 -1
  29. package/dist/shared/constants.d.ts +10 -3
  30. package/dist/shared/constants.js +1 -1
  31. package/dist/shared/parse-chunk-key.d.ts +1 -0
  32. package/dist/shared/parse-chunk-key.js +2 -1
  33. package/dist/shared/validate-outname.d.ts +6 -1
  34. package/dist/shared/validate-outname.js +3 -2
  35. package/package.json +8 -8
  36. package/remotionlambda-arm64.zip +0 -0
@@ -48,6 +48,6 @@ export type InnerRenderMediaOnLambdaInput = {
48
48
  colorSpace: ColorSpace;
49
49
  deleteAfter: DeleteAfter | null;
50
50
  } & ToOptions<typeof BrowserSafeApis.optionsMap.renderMediaOnLambda>;
51
- export declare const makeLambdaRenderMediaPayload: ({ rendererFunctionName, frameRange, framesPerLambda, forceBucketName: bucketName, codec, composition, serveUrl, imageFormat, inputProps, region, crf, envVariables, pixelFormat, proResProfile, x264Preset, maxRetries, privacy, logLevel, outName, timeoutInMilliseconds, chromiumOptions, scale, everyNthFrame, numberOfGifLoops, audioBitrate, concurrencyPerLambda, audioCodec, forceHeight, forceWidth, webhook, videoBitrate, encodingMaxRate, encodingBufferSize, downloadBehavior, muted, overwrite, jpegQuality, offthreadVideoCacheSizeInBytes, deleteAfter, colorSpace, }: InnerRenderMediaOnLambdaInput) => Promise<LambdaStartPayload>;
51
+ export declare const makeLambdaRenderMediaPayload: ({ rendererFunctionName, frameRange, framesPerLambda, forceBucketName: bucketName, codec, composition, serveUrl, imageFormat, inputProps, region, crf, envVariables, pixelFormat, proResProfile, x264Preset, maxRetries, privacy, logLevel, outName, timeoutInMilliseconds, chromiumOptions, scale, everyNthFrame, numberOfGifLoops, audioBitrate, concurrencyPerLambda, audioCodec, forceHeight, forceWidth, webhook, videoBitrate, encodingMaxRate, encodingBufferSize, downloadBehavior, muted, overwrite, jpegQuality, offthreadVideoCacheSizeInBytes, deleteAfter, colorSpace, preferLossless, }: InnerRenderMediaOnLambdaInput) => Promise<LambdaStartPayload>;
52
52
  export declare const getRenderProgressPayload: ({ bucketName, renderId, s3OutputProvider, logLevel, }: GetRenderProgressInput) => LambdaStatusPayload;
53
53
  export declare const makeLambdaRenderStillPayload: ({ serveUrl, inputProps, imageFormat, envVariables, quality, jpegQuality, region, maxRetries, composition, privacy, frame, logLevel, outName, timeoutInMilliseconds, chromiumOptions, scale, downloadBehavior, forceHeight, forceWidth, forceBucketName, dumpBrowserLogs, offthreadVideoCacheSizeInBytes, deleteAfter, }: RenderStillOnLambdaInput) => Promise<LambdaPayloads[LambdaRoutines.still]>;
@@ -9,7 +9,7 @@ const validate_frames_per_lambda_1 = require("../shared/validate-frames-per-lamb
9
9
  const validate_lambda_codec_1 = require("../shared/validate-lambda-codec");
10
10
  const validate_serveurl_1 = require("../shared/validate-serveurl");
11
11
  const validate_webhook_1 = require("../shared/validate-webhook");
12
- const makeLambdaRenderMediaPayload = async ({ rendererFunctionName, frameRange, framesPerLambda, forceBucketName: bucketName, codec, composition, serveUrl, imageFormat, inputProps, region, crf, envVariables, pixelFormat, proResProfile, x264Preset, maxRetries, privacy, logLevel, outName, timeoutInMilliseconds, chromiumOptions, scale, everyNthFrame, numberOfGifLoops, audioBitrate, concurrencyPerLambda, audioCodec, forceHeight, forceWidth, webhook, videoBitrate, encodingMaxRate, encodingBufferSize, downloadBehavior, muted, overwrite, jpegQuality, offthreadVideoCacheSizeInBytes, deleteAfter, colorSpace, }) => {
12
+ const makeLambdaRenderMediaPayload = async ({ rendererFunctionName, frameRange, framesPerLambda, forceBucketName: bucketName, codec, composition, serveUrl, imageFormat, inputProps, region, crf, envVariables, pixelFormat, proResProfile, x264Preset, maxRetries, privacy, logLevel, outName, timeoutInMilliseconds, chromiumOptions, scale, everyNthFrame, numberOfGifLoops, audioBitrate, concurrencyPerLambda, audioCodec, forceHeight, forceWidth, webhook, videoBitrate, encodingMaxRate, encodingBufferSize, downloadBehavior, muted, overwrite, jpegQuality, offthreadVideoCacheSizeInBytes, deleteAfter, colorSpace, preferLossless, }) => {
13
13
  const actualCodec = (0, validate_lambda_codec_1.validateLambdaCodec)(codec);
14
14
  (0, validate_serveurl_1.validateServeUrl)(serveUrl);
15
15
  (0, validate_frames_per_lambda_1.validateFramesPerLambda)({
@@ -70,6 +70,7 @@ const makeLambdaRenderMediaPayload = async ({ rendererFunctionName, frameRange,
70
70
  offthreadVideoCacheSizeInBytes: offthreadVideoCacheSizeInBytes !== null && offthreadVideoCacheSizeInBytes !== void 0 ? offthreadVideoCacheSizeInBytes : null,
71
71
  deleteAfter: deleteAfter !== null && deleteAfter !== void 0 ? deleteAfter : null,
72
72
  colorSpace: colorSpace !== null && colorSpace !== void 0 ? colorSpace : 'default',
73
+ preferLossless: preferLossless !== null && preferLossless !== void 0 ? preferLossless : false,
73
74
  };
74
75
  };
75
76
  exports.makeLambdaRenderMediaPayload = makeLambdaRenderMediaPayload;
@@ -68,7 +68,7 @@ exports.internalRenderMediaOnLambdaRaw = internalRenderMediaOnLambdaRaw;
68
68
  * @returns {Promise<RenderMediaOnLambdaOutput>} See documentation for detailed structure
69
69
  */
70
70
  const renderMediaOnLambda = (options) => {
71
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10;
71
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11;
72
72
  const wrapped = pure_1.NoReactAPIs.wrapWithErrorHandling(exports.internalRenderMediaOnLambdaRaw);
73
73
  if (options.quality) {
74
74
  throw new Error('quality has been renamed to jpegQuality. Please rename the option.');
@@ -115,6 +115,7 @@ const renderMediaOnLambda = (options) => {
115
115
  webhook: (_8 = options.webhook) !== null && _8 !== void 0 ? _8 : null,
116
116
  x264Preset: (_9 = options.x264Preset) !== null && _9 !== void 0 ? _9 : null,
117
117
  deleteAfter: (_10 = options.deleteAfter) !== null && _10 !== void 0 ? _10 : null,
118
+ preferLossless: (_11 = options.preferLossless) !== null && _11 !== void 0 ? _11 : false,
118
119
  });
119
120
  };
120
121
  exports.renderMediaOnLambda = renderMediaOnLambda;
@@ -10,7 +10,7 @@ type ChunkProgress = {
10
10
  framesRendered: number;
11
11
  totalFrames: number | null;
12
12
  totalChunks: number | null;
13
- chunksInvoked: number;
13
+ chunksEncoded: number;
14
14
  };
15
15
  type MultiRenderProgress = {
16
16
  lambdaInvokeProgress: LambdaInvokeProgress;
@@ -19,11 +19,11 @@ const makeInvokeProgress = (invokeProgress, totalSteps, retriesInfo) => {
19
19
  ].join(' ');
20
20
  };
21
21
  const makeRenderProgress = ({ chunkProgress, totalSteps, }) => {
22
- const { chunksInvoked, totalChunks, doneIn } = chunkProgress;
22
+ const { chunksEncoded, totalChunks, doneIn } = chunkProgress;
23
23
  const renderProgress = chunkProgress.totalFrames === null
24
24
  ? 0
25
25
  : chunkProgress.framesRendered / chunkProgress.totalFrames;
26
- const encodingProgress = totalChunks === null ? 0 : chunksInvoked / totalChunks;
26
+ const encodingProgress = totalChunks === null ? 0 : chunksEncoded / totalChunks;
27
27
  const frames = chunkProgress.totalFrames === null
28
28
  ? null
29
29
  : `(${chunkProgress.framesRendered}/${chunkProgress.totalFrames})`;
@@ -106,11 +106,11 @@ const makeMultiProgressFromStatus = (status) => {
106
106
  var _a, _b, _c, _d, _e, _f;
107
107
  return {
108
108
  chunkProgress: {
109
- chunksInvoked: status.chunks,
109
+ chunksEncoded: status.chunks,
110
110
  totalChunks: (_b = (_a = status.renderMetadata) === null || _a === void 0 ? void 0 : _a.totalChunks) !== null && _b !== void 0 ? _b : null,
111
111
  doneIn: status.timeToFinishChunks,
112
112
  framesRendered: status.framesRendered,
113
- totalFrames: status.renderMetadata
113
+ totalFrames: status.renderMetadata && status.renderMetadata.type === 'video'
114
114
  ? renderer_1.RenderInternals.getFramesToRender(status.renderMetadata.frameRange, status.renderMetadata.everyNthFrame).length
115
115
  : null,
116
116
  },
@@ -24,11 +24,11 @@ const log_1 = require("../../log");
24
24
  const progress_1 = require("./progress");
25
25
  exports.RENDER_COMMAND = 'render';
26
26
  function getTotalFrames(status) {
27
- return status.renderMetadata
27
+ return status.renderMetadata && status.renderMetadata.type === 'video'
28
28
  ? renderer_1.RenderInternals.getFramesToRender(status.renderMetadata.frameRange, status.renderMetadata.everyNthFrame).length
29
29
  : null;
30
30
  }
31
- const { x264Option, audioBitrateOption, offthreadVideoCacheSizeInBytesOption, scaleOption, crfOption, jpegQualityOption, videoBitrateOption, mutedOption, colorSpaceOption, deleteAfterOption, enableMultiprocessOnLinuxOption, glOption, headlessOption, numberOfGifLoopsOption, encodingMaxRateOption, encodingBufferSizeOption, delayRenderTimeoutInMillisecondsOption, overwriteOption, binariesDirectoryOption, } = client_1.BrowserSafeApis.options;
31
+ const { x264Option, audioBitrateOption, offthreadVideoCacheSizeInBytesOption, scaleOption, crfOption, jpegQualityOption, videoBitrateOption, mutedOption, colorSpaceOption, deleteAfterOption, enableMultiprocessOnLinuxOption, glOption, headlessOption, numberOfGifLoopsOption, encodingMaxRateOption, encodingBufferSizeOption, delayRenderTimeoutInMillisecondsOption, overwriteOption, binariesDirectoryOption, preferLosslessOption, } = client_1.BrowserSafeApis.options;
32
32
  const renderCommand = async (args, remotionRoot, logLevel) => {
33
33
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
34
34
  const serveUrl = args[0];
@@ -101,6 +101,9 @@ const renderCommand = async (args, remotionRoot, logLevel) => {
101
101
  const binariesDirectory = binariesDirectoryOption.getValue({
102
102
  commandLine: cli_1.CliInternals.parsedCli,
103
103
  }).value;
104
+ const preferLossless = preferLosslessOption.getValue({
105
+ commandLine: cli_1.CliInternals.parsedCli,
106
+ }).value;
104
107
  const chromiumOptions = {
105
108
  disableWebSecurity,
106
109
  enableMultiProcessOnLinux,
@@ -216,12 +219,13 @@ const renderCommand = async (args, remotionRoot, logLevel) => {
216
219
  : null,
217
220
  rendererFunctionName: (_j = args_1.parsedLambdaCli['renderer-function-name']) !== null && _j !== void 0 ? _j : null,
218
221
  forceBucketName: (_k = args_1.parsedLambdaCli['force-bucket-name']) !== null && _k !== void 0 ? _k : null,
219
- audioCodec: cli_1.CliInternals.parsedCli['audio-codec'],
222
+ audioCodec: cli_1.CliInternals.parsedCli[client_1.BrowserSafeApis.options.audioCodecOption.cliFlag],
220
223
  deleteAfter: deleteAfter !== null && deleteAfter !== void 0 ? deleteAfter : null,
221
224
  colorSpace,
222
225
  downloadBehavior: { type: 'play-in-browser' },
223
226
  offthreadVideoCacheSizeInBytes: offthreadVideoCacheSizeInBytes !== null && offthreadVideoCacheSizeInBytes !== void 0 ? offthreadVideoCacheSizeInBytes : null,
224
227
  x264Preset: x264Preset !== null && x264Preset !== void 0 ? x264Preset : null,
228
+ preferLossless,
225
229
  });
226
230
  const totalSteps = downloadName ? 6 : 5;
227
231
  const progressBar = cli_1.CliInternals.createOverwriteableCliOutput({
@@ -1,2 +1,3 @@
1
- import type { AudioCodec, Codec } from '@remotion/renderer';
2
- export declare const canConcatSeamlessly: (audioCodec: AudioCodec | null, codec: Codec) => boolean;
1
+ import type { AudioCodec } from '@remotion/renderer';
2
+ export declare const canConcatAudioSeamlessly: (_audioCodec: AudioCodec | null) => boolean;
3
+ export declare const canConcatVideoSeamlessly: (codec: string) => boolean;
@@ -1,7 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.canConcatSeamlessly = void 0;
4
- const canConcatSeamlessly = (audioCodec, codec) => {
5
- return audioCodec === 'aac' && codec === 'h264';
3
+ exports.canConcatVideoSeamlessly = exports.canConcatAudioSeamlessly = void 0;
4
+ // Temporarily disable seamless audio concat
5
+ // Cannot do WAV yet, because currently assumes AAC in+outpoint
6
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
7
+ const canConcatAudioSeamlessly = (_audioCodec) => {
8
+ return false;
6
9
  };
7
- exports.canConcatSeamlessly = canConcatSeamlessly;
10
+ exports.canConcatAudioSeamlessly = canConcatAudioSeamlessly;
11
+ const canConcatVideoSeamlessly = (codec) => {
12
+ return codec === 'h264';
13
+ };
14
+ exports.canConcatVideoSeamlessly = canConcatVideoSeamlessly;
@@ -1,4 +1,4 @@
1
- import type { AudioCodec, LogLevel } from '@remotion/renderer';
1
+ import type { AudioCodec, CancelSignal, LogLevel } from '@remotion/renderer';
2
2
  import type { AwsRegion } from '../../pricing/aws-regions';
3
3
  import type { LambdaCodec } from '../../shared/validate-lambda-codec';
4
4
  import type { EnhancedErrorInfo } from './write-lambda-error';
@@ -12,7 +12,7 @@ export declare const getAllFilesS3: ({ bucket, expectedFiles, outdir, renderId,
12
12
  onErrors: (errors: EnhancedErrorInfo[]) => void;
13
13
  logLevel: LogLevel;
14
14
  }) => Promise<string[]>;
15
- export declare const concatVideosS3: ({ onProgress, numberOfFrames, codec, fps, numberOfGifLoops, files, outdir, audioCodec, audioBitrate, logLevel, binariesDirectory, }: {
15
+ export declare const concatVideosS3: ({ onProgress, numberOfFrames, codec, fps, numberOfGifLoops, files, outdir, audioCodec, audioBitrate, logLevel, framesPerLambda, binariesDirectory, cancelSignal, preferLossless, }: {
16
16
  onProgress: (frames: number) => void;
17
17
  numberOfFrames: number;
18
18
  codec: LambdaCodec;
@@ -23,7 +23,10 @@ export declare const concatVideosS3: ({ onProgress, numberOfFrames, codec, fps,
23
23
  audioCodec: AudioCodec | null;
24
24
  audioBitrate: string | null;
25
25
  logLevel: LogLevel;
26
+ framesPerLambda: number;
26
27
  binariesDirectory: string | null;
28
+ cancelSignal: CancelSignal | undefined;
29
+ preferLossless: boolean;
27
30
  }) => Promise<{
28
31
  outfile: string;
29
32
  cleanupChunksProm: Promise<void>;
@@ -28,6 +28,7 @@ const renderer_1 = require("@remotion/renderer");
28
28
  const node_fs_1 = __importStar(require("node:fs"));
29
29
  const node_path_1 = __importStar(require("node:path"));
30
30
  const constants_1 = require("../../shared/constants");
31
+ const can_concat_seamlessly_1 = require("./can-concat-seamlessly");
31
32
  const inspect_errors_1 = require("./inspect-errors");
32
33
  const io_1 = require("./io");
33
34
  const timer_1 = require("./timer");
@@ -56,7 +57,7 @@ const getAllFilesS3 = ({ bucket, expectedFiles, outdir, renderId, region, expect
56
57
  const downloaded = {};
57
58
  const getFiles = async () => {
58
59
  const prefix = (0, constants_1.rendersPrefix)(renderId);
59
- const lsTimer = (0, timer_1.timer)('Listing files');
60
+ const lsTimer = (0, timer_1.timer)('Listing files', logLevel);
60
61
  const contents = await (0, io_1.lambdaLs)({
61
62
  bucketName: bucket,
62
63
  prefix,
@@ -105,7 +106,7 @@ const getAllFilesS3 = ({ bucket, expectedFiles, outdir, renderId, region, expect
105
106
  }
106
107
  alreadyDownloading[key] = true;
107
108
  try {
108
- const downloadTimer = (0, timer_1.timer)('Downloading ' + key);
109
+ const downloadTimer = (0, timer_1.timer)('Downloading ' + key, logLevel);
109
110
  await downloadS3File({
110
111
  bucket,
111
112
  key,
@@ -133,28 +134,42 @@ const getAllFilesS3 = ({ bucket, expectedFiles, outdir, renderId, region, expect
133
134
  });
134
135
  };
135
136
  exports.getAllFilesS3 = getAllFilesS3;
136
- const concatVideosS3 = async ({ onProgress, numberOfFrames, codec, fps, numberOfGifLoops, files, outdir, audioCodec, audioBitrate, logLevel, binariesDirectory, }) => {
137
- const outfile = (0, node_path_1.join)(renderer_1.RenderInternals.tmpDir(constants_1.REMOTION_CONCATED_TOKEN), 'concat.' + renderer_1.RenderInternals.getFileExtensionFromCodec(codec, audioCodec));
138
- const combine = (0, timer_1.timer)('Combine videos');
137
+ const concatVideosS3 = async ({ onProgress, numberOfFrames, codec, fps, numberOfGifLoops, files, outdir, audioCodec, audioBitrate, logLevel, framesPerLambda, binariesDirectory, cancelSignal, preferLossless, }) => {
138
+ const outfile = (0, node_path_1.join)(renderer_1.RenderInternals.tmpDir(constants_1.REMOTION_CONCATED_TOKEN), `concat.${renderer_1.RenderInternals.getFileExtensionFromCodec(codec, audioCodec)}`);
139
+ const combine = (0, timer_1.timer)('Combine videos', logLevel);
139
140
  const filelistDir = renderer_1.RenderInternals.tmpDir(constants_1.REMOTION_FILELIST_TOKEN);
141
+ const chunkDurationInSeconds = framesPerLambda / fps;
142
+ const resolvedAudioCodec = renderer_1.RenderInternals.resolveAudioCodec({
143
+ setting: audioCodec,
144
+ codec,
145
+ preferLossless,
146
+ separateAudioTo: null,
147
+ });
148
+ const seamlessAudio = (0, can_concat_seamlessly_1.canConcatAudioSeamlessly)(resolvedAudioCodec);
149
+ const seamlessVideo = (0, can_concat_seamlessly_1.canConcatVideoSeamlessly)(codec);
140
150
  await renderer_1.RenderInternals.combineVideos({
141
151
  files,
142
152
  filelistDir,
143
153
  output: outfile,
144
- onProgress: (p) => onProgress(p),
154
+ onProgress,
145
155
  numberOfFrames,
146
156
  codec,
147
157
  fps,
148
158
  numberOfGifLoops,
149
- audioCodec,
159
+ resolvedAudioCodec,
150
160
  audioBitrate,
151
161
  indent: false,
152
162
  logLevel,
163
+ chunkDurationInSeconds,
153
164
  binariesDirectory,
165
+ cancelSignal,
166
+ seamlessAudio,
167
+ seamlessVideo,
154
168
  });
155
169
  combine.end();
156
170
  const cleanupChunksProm = node_fs_1.default.promises.rm(outdir, {
157
171
  recursive: true,
172
+ force: true,
158
173
  });
159
174
  return { outfile, cleanupChunksProm };
160
175
  };
@@ -9,6 +9,7 @@ const calculate_chunk_times_1 = require("./calculate-chunk-times");
9
9
  const get_files_to_delete_1 = require("./get-files-to-delete");
10
10
  const get_retry_stats_1 = require("./get-retry-stats");
11
11
  const get_time_to_finish_1 = require("./get-time-to-finish");
12
+ const render_has_audio_video_1 = require("./render-has-audio-video");
12
13
  const createPostRenderData = ({ renderId, region, memorySizeInMb, renderMetadata, contents, timeToEncode, errorExplanations, timeToDelete, outputFile, }) => {
13
14
  const initializedKeys = contents.filter((c) => { var _a; return (_a = c.Key) === null || _a === void 0 ? void 0 : _a.startsWith((0, constants_1.lambdaTimingsPrefix)(renderId)); });
14
15
  const parsedTimings = initializedKeys.map(({ Key }) => (0, parse_lambda_timings_key_1.parseLambdaTimingsKey)(Key));
@@ -42,6 +43,7 @@ const createPostRenderData = ({ renderId, region, memorySizeInMb, renderMetadata
42
43
  contents,
43
44
  renderId,
44
45
  });
46
+ const { hasAudio, hasVideo } = (0, render_has_audio_video_1.lambdaRenderHasAudioVideo)(renderMetadata);
45
47
  return {
46
48
  cost: {
47
49
  currency: 'USD',
@@ -63,6 +65,8 @@ const createPostRenderData = ({ renderId, region, memorySizeInMb, renderMetadata
63
65
  filesCleanedUp: (0, get_files_to_delete_1.getFilesToDelete)({
64
66
  chunkCount: renderMetadata.totalChunks,
65
67
  renderId,
68
+ hasAudio,
69
+ hasVideo,
66
70
  }).length,
67
71
  timeToEncode,
68
72
  timeToCleanUp: timeToDelete,
@@ -72,7 +76,9 @@ const createPostRenderData = ({ renderId, region, memorySizeInMb, renderMetadata
72
76
  type: 'absolute-time',
73
77
  }),
74
78
  retriesInfo,
75
- mostExpensiveFrameRanges: (0, get_most_expensive_chunks_1.getMostExpensiveChunks)(parsedTimings, renderMetadata.framesPerLambda, renderMetadata.frameRange[0], renderMetadata.frameRange[1]),
79
+ mostExpensiveFrameRanges: renderMetadata.type === 'still'
80
+ ? []
81
+ : (0, get_most_expensive_chunks_1.getMostExpensiveChunks)(parsedTimings, renderMetadata.framesPerLambda, renderMetadata.frameRange[0], renderMetadata.frameRange[1]),
76
82
  deleteAfter: renderMetadata.deleteAfter,
77
83
  estimatedBillingDurationInMilliseconds,
78
84
  };
@@ -25,7 +25,12 @@ const getExpectedOutName = (renderMetadata, bucketName, customCredentials) => {
25
25
  renderMetadata,
26
26
  });
27
27
  if (outNameValue) {
28
- (0, validate_outname_1.validateOutname)(outNameValue, renderMetadata.codec, renderMetadata.audioCodec);
28
+ (0, validate_outname_1.validateOutname)({
29
+ outName: outNameValue,
30
+ codec: renderMetadata.codec,
31
+ audioCodecSetting: renderMetadata.audioCodec,
32
+ separateAudioTo: null,
33
+ });
29
34
  return (0, defaults_1.customOutName)(renderMetadata.renderId, bucketName, outNameValue);
30
35
  }
31
36
  if (renderMetadata.type === 'still') {
@@ -1,8 +1,10 @@
1
1
  import type { _Object } from '@aws-sdk/client-s3';
2
2
  import type { CleanupInfo } from '../../shared/constants';
3
- export declare const getCleanupProgress: ({ contents, output, chunkCount, renderId, }: {
3
+ export declare const getCleanupProgress: ({ contents, output, chunkCount, renderId, hasAudio, hasVideo, }: {
4
4
  contents: _Object[];
5
5
  output: string | null;
6
6
  chunkCount: number;
7
7
  renderId: string;
8
+ hasAudio: boolean;
9
+ hasVideo: boolean;
8
10
  }) => null | CleanupInfo;
@@ -2,13 +2,15 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getCleanupProgress = void 0;
4
4
  const get_files_to_delete_1 = require("./get-files-to-delete");
5
- const getCleanupProgress = ({ contents, output, chunkCount, renderId, }) => {
5
+ const getCleanupProgress = ({ contents, output, chunkCount, renderId, hasAudio, hasVideo, }) => {
6
6
  if (output === null) {
7
7
  return null;
8
8
  }
9
9
  const filesToDelete = (0, get_files_to_delete_1.getFilesToDelete)({
10
10
  chunkCount,
11
11
  renderId,
12
+ hasAudio,
13
+ hasVideo,
12
14
  });
13
15
  const filesStillThere = contents.filter((c) => {
14
16
  return filesToDelete.find((f) => {
@@ -2,7 +2,9 @@ export type CleanupJob = {
2
2
  name: string;
3
3
  type: 'exact' | 'prefix';
4
4
  };
5
- export declare const getFilesToDelete: ({ chunkCount, renderId, }: {
5
+ export declare const getFilesToDelete: ({ chunkCount, renderId, hasVideo, hasAudio, }: {
6
6
  chunkCount: number;
7
7
  renderId: string;
8
+ hasVideo: boolean;
9
+ hasAudio: boolean;
8
10
  }) => CleanupJob[];
@@ -2,11 +2,21 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getFilesToDelete = void 0;
4
4
  const constants_1 = require("../../shared/constants");
5
- const getFilesToDelete = ({ chunkCount, renderId, }) => {
6
- const chunks = new Array(chunkCount).fill(true).map((_x, i) => (0, constants_1.chunkKeyForIndex)({
7
- index: i,
8
- renderId,
9
- }));
5
+ const getFilesToDelete = ({ chunkCount, renderId, hasVideo, hasAudio, }) => {
6
+ const videoChunks = hasVideo
7
+ ? new Array(chunkCount).fill(true).map((_x, i) => (0, constants_1.chunkKeyForIndex)({
8
+ index: i,
9
+ renderId,
10
+ type: 'video',
11
+ }))
12
+ : [];
13
+ const audioChunks = hasAudio
14
+ ? new Array(chunkCount).fill(true).map((_x, i) => (0, constants_1.chunkKeyForIndex)({
15
+ index: i,
16
+ renderId,
17
+ type: 'audio',
18
+ }))
19
+ : [];
10
20
  const lambdaTimings = new Array(chunkCount)
11
21
  .fill(true)
12
22
  .map((_x, i) => (0, constants_1.lambdaTimingsPrefixForChunk)(renderId, i));
@@ -15,7 +25,13 @@ const getFilesToDelete = ({ chunkCount, renderId, }) => {
15
25
  name: (0, constants_1.lambdaChunkInitializedPrefix)(renderId),
16
26
  type: 'prefix',
17
27
  },
18
- ...chunks.map((i) => {
28
+ ...videoChunks.map((i) => {
29
+ return {
30
+ name: i,
31
+ type: 'exact',
32
+ };
33
+ }),
34
+ ...audioChunks.map((i) => {
19
35
  return {
20
36
  name: i,
21
37
  type: 'exact',
@@ -5,6 +5,7 @@ const renderer_1 = require("@remotion/renderer");
5
5
  const no_react_1 = require("remotion/no-react");
6
6
  const constants_1 = require("../../shared/constants");
7
7
  const parse_chunk_key_1 = require("../../shared/parse-chunk-key");
8
+ const truthy_1 = require("../../shared/truthy");
8
9
  const calculate_chunk_times_1 = require("./calculate-chunk-times");
9
10
  const calculate_price_from_bucket_1 = require("./calculate-price-from-bucket");
10
11
  const check_if_render_exists_1 = require("./check-if-render-exists");
@@ -25,6 +26,7 @@ const get_time_to_finish_1 = require("./get-time-to-finish");
25
26
  const inspect_errors_1 = require("./inspect-errors");
26
27
  const io_1 = require("./io");
27
28
  const make_timeout_error_1 = require("./make-timeout-error");
29
+ const render_has_audio_video_1 = require("./render-has-audio-video");
28
30
  const getProgress = async ({ bucketName, renderId, expectedBucketOwner, region, memorySizeInMb, timeoutInMilliseconds, customCredentials, }) => {
29
31
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
30
32
  const postRenderData = await (0, get_post_render_data_1.getPostRenderData)({
@@ -35,7 +37,9 @@ const getProgress = async ({ bucketName, renderId, expectedBucketOwner, region,
35
37
  });
36
38
  if (postRenderData) {
37
39
  const outData = (0, expected_out_name_1.getExpectedOutName)(postRenderData.renderMetadata, bucketName, customCredentials);
38
- const totalFrameCount = renderer_1.RenderInternals.getFramesToRender(postRenderData.renderMetadata.frameRange, postRenderData.renderMetadata.everyNthFrame).length;
40
+ const totalFrameCount = postRenderData.renderMetadata.type === 'still'
41
+ ? 1
42
+ : renderer_1.RenderInternals.getFramesToRender(postRenderData.renderMetadata.frameRange, postRenderData.renderMetadata.everyNthFrame).length;
39
43
  return {
40
44
  framesRendered: totalFrameCount,
41
45
  bucket: bucketName,
@@ -122,16 +126,22 @@ const getProgress = async ({ bucketName, renderId, expectedBucketOwner, region,
122
126
  // overestimate the price, but will only have a miniscule effect (~0.2%)
123
127
  diskSizeInMb: constants_1.MAX_EPHEMERAL_STORAGE_IN_MB,
124
128
  });
129
+ const { hasAudio, hasVideo } = renderMetadata
130
+ ? (0, render_has_audio_video_1.lambdaRenderHasAudioVideo)(renderMetadata)
131
+ : { hasAudio: false, hasVideo: false };
125
132
  const cleanup = (0, get_cleanup_progress_1.getCleanupProgress)({
126
133
  chunkCount: (_c = renderMetadata === null || renderMetadata === void 0 ? void 0 : renderMetadata.totalChunks) !== null && _c !== void 0 ? _c : 0,
127
134
  contents,
128
135
  output: (_d = outputFile === null || outputFile === void 0 ? void 0 : outputFile.url) !== null && _d !== void 0 ? _d : null,
129
136
  renderId,
137
+ hasAudio,
138
+ hasVideo,
130
139
  });
131
140
  const timeToFinish = (0, get_time_to_finish_1.getTimeToFinish)({
132
141
  lastModified: (_e = outputFile === null || outputFile === void 0 ? void 0 : outputFile.lastModified) !== null && _e !== void 0 ? _e : null,
133
142
  renderMetadata,
134
143
  });
144
+ const chunkMultiplier = [hasAudio, hasVideo].filter(truthy_1.truthy).length;
135
145
  const chunks = contents.filter((c) => { var _a; return (_a = c.Key) === null || _a === void 0 ? void 0 : _a.startsWith((0, constants_1.chunkKey)(renderId)); });
136
146
  const framesRendered = renderMetadata
137
147
  ? (0, get_rendered_frames_progress_1.getRenderedFramesProgress)({
@@ -142,7 +152,8 @@ const getProgress = async ({ bucketName, renderId, expectedBucketOwner, region,
142
152
  renderId,
143
153
  })
144
154
  : 0;
145
- const allChunks = chunks.length === ((_f = renderMetadata === null || renderMetadata === void 0 ? void 0 : renderMetadata.totalChunks) !== null && _f !== void 0 ? _f : Infinity);
155
+ const allChunks = chunks.length / chunkMultiplier ===
156
+ ((_f = renderMetadata === null || renderMetadata === void 0 ? void 0 : renderMetadata.totalChunks) !== null && _f !== void 0 ? _f : Infinity);
146
157
  const renderSize = contents
147
158
  .map((c) => { var _a; return (_a = c.Size) !== null && _a !== void 0 ? _a : 0; })
148
159
  .reduce((a, b) => a + b, 0);
@@ -168,7 +179,7 @@ const getProgress = async ({ bucketName, renderId, expectedBucketOwner, region,
168
179
  });
169
180
  const chunkCount = outputFile
170
181
  ? (_g = renderMetadata === null || renderMetadata === void 0 ? void 0 : renderMetadata.totalChunks) !== null && _g !== void 0 ? _g : 0
171
- : chunks.length;
182
+ : chunks.length / chunkMultiplier;
172
183
  const availableChunks = chunks.map((c) => (0, parse_chunk_key_1.parseLambdaChunkKey)(c.Key));
173
184
  const missingChunks = renderMetadata
174
185
  ? new Array(renderMetadata.totalChunks)
@@ -16,9 +16,11 @@ const makeChunkMissingMessage = ({ missingChunks, renderMetadata, }) => {
16
16
  .map((ch) => {
17
17
  const isLastChunk = ch === renderMetadata.totalChunks - 1;
18
18
  const start = ch * renderMetadata.framesPerLambda;
19
- const end = isLastChunk
20
- ? renderMetadata.frameRange[1]
21
- : (ch + 1) * renderMetadata.framesPerLambda - 1;
19
+ const end = renderMetadata.type === 'still'
20
+ ? 0
21
+ : isLastChunk
22
+ ? renderMetadata.frameRange[1]
23
+ : (ch + 1) * renderMetadata.framesPerLambda - 1;
22
24
  const msg = `Chunk ${ch} (Frames ${start} - ${end})`;
23
25
  return [
24
26
  msg,
@@ -6,6 +6,7 @@ import type { LambdaCodec } from '../../shared/validate-lambda-codec';
6
6
  export type OnAllChunksAvailable = (options: {
7
7
  inputProps: SerializedInputProps;
8
8
  serializedResolvedProps: SerializedInputProps;
9
+ framesPerLambda: number;
9
10
  }) => void;
10
11
  export declare const mergeChunksAndFinishRender: (options: {
11
12
  bucketName: string;
@@ -28,5 +29,7 @@ export declare const mergeChunksAndFinishRender: (options: {
28
29
  onAllChunks: OnAllChunksAvailable;
29
30
  audioBitrate: string | null;
30
31
  logLevel: LogLevel;
32
+ framesPerLambda: number;
31
33
  binariesDirectory: string | null;
34
+ preferLossless: boolean;
32
35
  }) => Promise<PostRenderData>;
@@ -10,6 +10,7 @@ const node_fs_1 = require("node:fs");
10
10
  const node_path_1 = require("node:path");
11
11
  const cleanup_serialized_input_props_1 = require("../../shared/cleanup-serialized-input-props");
12
12
  const constants_1 = require("../../shared/constants");
13
+ const truthy_1 = require("../../shared/truthy");
13
14
  const concat_videos_1 = require("./concat-videos");
14
15
  const create_post_render_data_1 = require("./create-post-render-data");
15
16
  const delete_chunks_1 = require("./delete-chunks");
@@ -18,6 +19,8 @@ const get_files_to_delete_1 = require("./get-files-to-delete");
18
19
  const get_output_url_from_metadata_1 = require("./get-output-url-from-metadata");
19
20
  const inspect_errors_1 = require("./inspect-errors");
20
21
  const io_1 = require("./io");
22
+ const render_has_audio_video_1 = require("./render-has-audio-video");
23
+ const timer_1 = require("./timer");
21
24
  const write_lambda_error_1 = require("./write-lambda-error");
22
25
  const write_post_render_data_1 = require("./write-post-render-data");
23
26
  const mergeChunksAndFinishRender = async (options) => {
@@ -76,9 +79,12 @@ const mergeChunksAndFinishRender = async (options) => {
76
79
  });
77
80
  }
78
81
  (0, node_fs_1.mkdirSync)(outdir);
82
+ const { hasAudio, hasVideo } = (0, render_has_audio_video_1.lambdaRenderHasAudioVideo)(options.renderMetadata);
83
+ const chunkMultiplier = [hasAudio, hasVideo].filter(truthy_1.truthy).length;
84
+ const expectedFiles = chunkMultiplier * options.chunkCount;
79
85
  const files = await (0, concat_videos_1.getAllFilesS3)({
80
86
  bucket: options.bucketName,
81
- expectedFiles: options.chunkCount,
87
+ expectedFiles,
82
88
  outdir,
83
89
  renderId: options.renderId,
84
90
  region: (0, get_current_region_1.getCurrentRegionInFunction)(),
@@ -89,6 +95,7 @@ const mergeChunksAndFinishRender = async (options) => {
89
95
  options.onAllChunks({
90
96
  inputProps: options.inputProps,
91
97
  serializedResolvedProps: options.serializedResolvedProps,
98
+ framesPerLambda: options.framesPerLambda,
92
99
  });
93
100
  const encodingStart = Date.now();
94
101
  const { outfile, cleanupChunksProm } = await (0, concat_videos_1.concatVideosS3)({
@@ -102,10 +109,14 @@ const mergeChunksAndFinishRender = async (options) => {
102
109
  audioCodec: options.audioCodec,
103
110
  audioBitrate: options.audioBitrate,
104
111
  logLevel: options.logLevel,
112
+ framesPerLambda: options.framesPerLambda,
105
113
  binariesDirectory: options.binariesDirectory,
114
+ cancelSignal: undefined,
115
+ preferLossless: options.preferLossless,
106
116
  });
107
117
  const encodingStop = Date.now();
108
118
  const outputSize = fs_1.default.statSync(outfile);
119
+ const writeToS3 = (0, timer_1.timer)(`Writing to S3 (${outputSize.size} bytes)`, options.logLevel);
109
120
  await (0, io_1.lambdaWriteFile)({
110
121
  bucketName: options.renderBucketName,
111
122
  key: options.key,
@@ -116,6 +127,7 @@ const mergeChunksAndFinishRender = async (options) => {
116
127
  downloadBehavior: options.downloadBehavior,
117
128
  customCredentials: options.customCredentials,
118
129
  });
130
+ writeToS3.end();
119
131
  const contents = await (0, io_1.lambdaLs)({
120
132
  bucketName: options.bucketName,
121
133
  prefix: (0, constants_1.rendersPrefix)(options.renderId),
@@ -142,6 +154,8 @@ const mergeChunksAndFinishRender = async (options) => {
142
154
  const jobs = (0, get_files_to_delete_1.getFilesToDelete)({
143
155
  chunkCount: options.chunkCount,
144
156
  renderId: options.renderId,
157
+ hasAudio,
158
+ hasVideo,
145
159
  });
146
160
  const deletProm = options.logLevel === 'verbose'
147
161
  ? Promise.resolve(0)
@@ -1,3 +1,4 @@
1
- export declare const timer: (label: string) => {
1
+ import type { LogLevel } from '@remotion/renderer';
2
+ export declare const timer: (label: string, logLevel: LogLevel) => {
2
3
  end: () => void;
3
4
  };
@@ -1,16 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.timer = void 0;
4
+ const renderer_1 = require("@remotion/renderer");
4
5
  const formatTime = (time) => {
5
6
  return time + 'ms';
6
7
  };
7
- const timer = (label) => {
8
+ const timer = (label, logLevel) => {
8
9
  const start = Date.now();
10
+ renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel }, `${label} - start\n`);
9
11
  return {
10
12
  end: () => {
11
13
  const end = Date.now();
12
14
  const time = end - start;
13
- process.stdout.write(`${label} - ${formatTime(time)}\n`);
15
+ renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel }, `${label} - ${formatTime(time)}\n`);
14
16
  },
15
17
  };
16
18
  };
@@ -110,7 +110,12 @@ const innerLaunchHandler = async ({ functionName, params, options, onAllChunksAv
110
110
  framesPerLambda,
111
111
  durationInFrames: frameCount.length,
112
112
  });
113
- (0, validate_outname_1.validateOutname)(params.outName, params.codec, params.audioCodec);
113
+ (0, validate_outname_1.validateOutname)({
114
+ outName: params.outName,
115
+ codec: params.codec,
116
+ audioCodecSetting: params.audioCodec,
117
+ separateAudioTo: null,
118
+ });
114
119
  (0, validate_privacy_1.validatePrivacy)(params.privacy, true);
115
120
  renderer_1.RenderInternals.validatePuppeteerTimeout(params.timeoutInMilliseconds);
116
121
  const { chunks } = (0, plan_frame_ranges_1.planFrameRanges)({
@@ -122,7 +127,7 @@ const innerLaunchHandler = async ({ functionName, params, options, onAllChunksAv
122
127
  throw new Error(`Too many functions: This render would cause ${chunks.length} functions to spawn. We limit this amount to ${constants_1.MAX_FUNCTIONS_PER_RENDER} functions as more would result in diminishing returns. Values set: frameCount = ${frameCount}, framesPerLambda=${framesPerLambda}. See ${docs_url_1.DOCS_URL}/docs/lambda/concurrency#too-many-functions for help.`);
123
128
  }
124
129
  const sortedChunks = chunks.slice().sort((a, b) => a[0] - b[0]);
125
- const reqSend = (0, timer_1.timer)('sending off requests');
130
+ const reqSend = (0, timer_1.timer)('sending off requests', params.logLevel);
126
131
  const serializedResolved = (0, compress_props_1.serializeOrThrow)(comp.props, 'resolved-props');
127
132
  const needsToUpload = (0, compress_props_1.getNeedsToUpload)('video-or-audio', [
128
133
  serializedResolved.length,
@@ -181,6 +186,7 @@ const innerLaunchHandler = async ({ functionName, params, options, onAllChunksAv
181
186
  offthreadVideoCacheSizeInBytes: params.offthreadVideoCacheSizeInBytes,
182
187
  deleteAfter: params.deleteAfter,
183
188
  colorSpace: params.colorSpace,
189
+ preferLossless: params.preferLossless,
184
190
  };
185
191
  return payload;
186
192
  });
@@ -216,6 +222,7 @@ const innerLaunchHandler = async ({ functionName, params, options, onAllChunksAv
216
222
  numberOfGifLoops: params.numberOfGifLoops,
217
223
  downloadBehavior: params.downloadBehavior,
218
224
  audioBitrate: params.audioBitrate,
225
+ muted: params.muted,
219
226
  };
220
227
  const { key, renderBucketName, customCredentials } = (0, expected_out_name_1.getExpectedOutName)(renderMetadata, params.bucketName, typeof params.outName === 'string' || typeof params.outName === 'undefined'
221
228
  ? null
@@ -276,7 +283,9 @@ const innerLaunchHandler = async ({ functionName, params, options, onAllChunksAv
276
283
  onAllChunks: onAllChunksAvailable,
277
284
  audioBitrate: params.audioBitrate,
278
285
  logLevel: params.logLevel,
286
+ framesPerLambda,
279
287
  binariesDirectory: null,
288
+ preferLossless: params.preferLossless,
280
289
  });
281
290
  return postRenderData;
282
291
  };
@@ -307,6 +316,8 @@ const launchHandler = async (params, options) => {
307
316
  serializedResolvedProps: allChunksAvailable.serializedResolvedProps,
308
317
  inputProps: allChunksAvailable.inputProps,
309
318
  logLevel: params.logLevel,
319
+ framesPerLambda: allChunksAvailable.framesPerLambda,
320
+ preferLossless: params.preferLossless,
310
321
  },
311
322
  retries: 2,
312
323
  });
@@ -400,8 +411,12 @@ const launchHandler = async (params, options) => {
400
411
  functionName,
401
412
  params,
402
413
  options,
403
- onAllChunksAvailable: ({ inputProps, serializedResolvedProps }) => {
404
- allChunksAvailable = { inputProps, serializedResolvedProps };
414
+ onAllChunksAvailable: ({ inputProps, serializedResolvedProps, framesPerLambda, }) => {
415
+ allChunksAvailable = {
416
+ inputProps,
417
+ serializedResolvedProps,
418
+ framesPerLambda,
419
+ };
405
420
  },
406
421
  });
407
422
  clearTimeout(webhookDueToTimeout);
@@ -23,6 +23,9 @@ const mergeHandler = async (params, options) => {
23
23
  if (!renderMetadata.codec) {
24
24
  throw new Error('expected codec');
25
25
  }
26
+ if (renderMetadata.type === 'still') {
27
+ throw new Error('Cannot merge stills');
28
+ }
26
29
  const { key, renderBucketName, customCredentials } = (0, expected_out_name_1.getExpectedOutName)(renderMetadata, params.bucketName, typeof params.outName === 'string' || typeof params.outName === 'undefined'
27
30
  ? null
28
31
  : (_b = (_a = params.outName) === null || _a === void 0 ? void 0 : _a.s3OutputProvider) !== null && _b !== void 0 ? _b : null);
@@ -51,7 +54,9 @@ const mergeHandler = async (params, options) => {
51
54
  },
52
55
  audioBitrate: renderMetadata.audioBitrate,
53
56
  logLevel: params.logLevel,
57
+ framesPerLambda: params.framesPerLambda,
54
58
  binariesDirectory: null,
59
+ preferLossless: params.preferLossless,
55
60
  });
56
61
  return { type: 'success', postRenderData };
57
62
  };
@@ -13,7 +13,9 @@ const chunk_progress_1 = require("../shared/chunk-progress");
13
13
  const compress_props_1 = require("../shared/compress-props");
14
14
  const constants_1 = require("../shared/constants");
15
15
  const is_flaky_error_1 = require("../shared/is-flaky-error");
16
+ const truthy_1 = require("../shared/truthy");
16
17
  const why_is_node_running_1 = require("../shared/why-is-node-running");
18
+ const can_concat_seamlessly_1 = require("./helpers/can-concat-seamlessly");
17
19
  const get_browser_instance_1 = require("./helpers/get-browser-instance");
18
20
  const get_chromium_executable_path_1 = require("./helpers/get-chromium-executable-path");
19
21
  const get_current_region_1 = require("./helpers/get-current-region");
@@ -59,13 +61,30 @@ const renderHandler = async (params, options, logs) => {
59
61
  startDate: start,
60
62
  };
61
63
  const outdir = renderer_1.RenderInternals.tmpDir(constants_1.RENDERER_PATH_TOKEN);
62
- const chunkCodec = params.codec === 'gif' || params.codec === 'h264'
63
- ? 'h264-mkv'
64
- : params.codec;
65
- const outputLocation = node_path_1.default.join(outdir, `localchunk-${String(params.chunk).padStart(8, '0')}.${renderer_1.RenderInternals.getFileExtensionFromCodec(chunkCodec, renderer_1.RenderInternals.getDefaultAudioCodec({
64
+ const chunk = `localchunk-${String(params.chunk).padStart(8, '0')}`;
65
+ const defaultAudioCodec = renderer_1.RenderInternals.getDefaultAudioCodec({
66
66
  codec: params.codec,
67
- preferLossless: true,
68
- }))}`);
67
+ preferLossless: params.preferLossless,
68
+ });
69
+ const seamlessAudio = (0, can_concat_seamlessly_1.canConcatAudioSeamlessly)(defaultAudioCodec);
70
+ const seamlessVideo = (0, can_concat_seamlessly_1.canConcatVideoSeamlessly)(params.codec);
71
+ renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel: params.logLevel }, `Preparing for rendering a chunk. Audio = ${seamlessAudio ? 'seamless' : 'normal'}, Video = ${seamlessVideo ? 'seamless' : 'normal'}`, params.logLevel);
72
+ const chunkCodec = params.codec === 'gif'
73
+ ? 'h264-mkv'
74
+ : seamlessVideo
75
+ ? 'h264-ts'
76
+ : params.codec;
77
+ const audioCodec = seamlessAudio
78
+ ? defaultAudioCodec
79
+ : 'pcm-16';
80
+ const videoExtension = renderer_1.RenderInternals.getFileExtensionFromCodec(chunkCodec, audioCodec);
81
+ const audioExtension = audioCodec
82
+ ? renderer_1.RenderInternals.getExtensionFromAudioCodec(audioCodec)
83
+ : null;
84
+ const videoOutputLocation = node_path_1.default.join(outdir, `${chunk}.${videoExtension}`);
85
+ const audioOutputLocation = audioExtension
86
+ ? node_path_1.default.join(outdir, `${chunk}.${audioExtension}`)
87
+ : null;
69
88
  const resolvedProps = await resolvedPropsPromise;
70
89
  const serializedInputPropsWithCustomSchema = await inputPropsPromise;
71
90
  await new Promise((resolve, reject) => {
@@ -126,7 +145,7 @@ const renderHandler = async (params, options, logs) => {
126
145
  onBrowserLog: (log) => {
127
146
  logs.push(log);
128
147
  },
129
- outputLocation,
148
+ outputLocation: videoOutputLocation,
130
149
  codec: chunkCodec,
131
150
  crf: (_c = params.crf) !== null && _c !== void 0 ? _c : null,
132
151
  pixelFormat: (_d = params.pixelFormat) !== null && _d !== void 0 ? _d : renderer_1.RenderInternals.DEFAULT_PIXEL_FORMAT,
@@ -146,11 +165,8 @@ const renderHandler = async (params, options, logs) => {
146
165
  videoBitrate: params.videoBitrate,
147
166
  encodingBufferSize: params.encodingBufferSize,
148
167
  encodingMaxRate: params.encodingMaxRate,
149
- // Lossless flag takes priority over audio codec
150
- // https://github.com/remotion-dev/remotion/issues/1647
151
- // Special flag only in Lambda renderer which improves the audio quality
152
- audioCodec: null,
153
- preferLossless: true,
168
+ audioCodec,
169
+ preferLossless: params.preferLossless,
154
170
  browserExecutable: (0, get_chromium_executable_path_1.executablePath)(),
155
171
  cancelSignal: undefined,
156
172
  disallowParallelEncoding: false,
@@ -163,6 +179,8 @@ const renderHandler = async (params, options, logs) => {
163
179
  colorSpace: params.colorSpace,
164
180
  finishRenderProgress: () => undefined,
165
181
  binariesDirectory: null,
182
+ separateAudioTo: audioOutputLocation,
183
+ forSeamlessAacConcatenation: seamlessAudio,
166
184
  })
167
185
  .then(({ slowestFrames }) => {
168
186
  console.log(`Slowest frames:`);
@@ -180,23 +198,45 @@ const renderHandler = async (params, options, logs) => {
180
198
  };
181
199
  renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel: params.logLevel }, 'Writing chunk to S3');
182
200
  const writeStart = Date.now();
183
- await (0, io_1.lambdaWriteFile)({
184
- bucketName: params.bucketName,
185
- key: (0, constants_1.chunkKeyForIndex)({
186
- renderId: params.renderId,
187
- index: params.chunk,
201
+ await Promise.all([
202
+ (0, io_1.lambdaWriteFile)({
203
+ bucketName: params.bucketName,
204
+ key: (0, constants_1.chunkKeyForIndex)({
205
+ renderId: params.renderId,
206
+ index: params.chunk,
207
+ type: 'video',
208
+ }),
209
+ body: node_fs_1.default.createReadStream(videoOutputLocation),
210
+ region: (0, get_current_region_1.getCurrentRegionInFunction)(),
211
+ privacy: params.privacy,
212
+ expectedBucketOwner: options.expectedBucketOwner,
213
+ downloadBehavior: null,
214
+ customCredentials: null,
188
215
  }),
189
- body: node_fs_1.default.createReadStream(outputLocation),
190
- region: (0, get_current_region_1.getCurrentRegionInFunction)(),
191
- privacy: params.privacy,
192
- expectedBucketOwner: options.expectedBucketOwner,
193
- downloadBehavior: null,
194
- customCredentials: null,
195
- });
216
+ audioOutputLocation
217
+ ? (0, io_1.lambdaWriteFile)({
218
+ bucketName: params.bucketName,
219
+ key: (0, constants_1.chunkKeyForIndex)({
220
+ renderId: params.renderId,
221
+ index: params.chunk,
222
+ type: 'audio',
223
+ }),
224
+ body: node_fs_1.default.createReadStream(audioOutputLocation),
225
+ region: (0, get_current_region_1.getCurrentRegionInFunction)(),
226
+ privacy: params.privacy,
227
+ expectedBucketOwner: options.expectedBucketOwner,
228
+ downloadBehavior: null,
229
+ customCredentials: null,
230
+ })
231
+ : null,
232
+ ]);
196
233
  renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel: params.logLevel }, `Wrote chunk to S3 (${Date.now() - writeStart}ms)`);
197
234
  renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel: params.logLevel }, 'Cleaning up and writing timings');
198
235
  await Promise.all([
199
- node_fs_1.default.promises.rm(outputLocation, { recursive: true }),
236
+ node_fs_1.default.promises.rm(videoOutputLocation, { recursive: true }),
237
+ audioOutputLocation
238
+ ? node_fs_1.default.promises.rm(audioOutputLocation, { recursive: true })
239
+ : null,
200
240
  node_fs_1.default.promises.rm(outputPath, { recursive: true }),
201
241
  (0, io_1.lambdaWriteFile)({
202
242
  bucketName: params.bucketName,
@@ -213,7 +253,7 @@ const renderHandler = async (params, options, logs) => {
213
253
  downloadBehavior: null,
214
254
  customCredentials: null,
215
255
  }),
216
- ]);
256
+ ].filter(truthy_1.truthy));
217
257
  renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel: params.logLevel }, 'Done!');
218
258
  return {};
219
259
  };
@@ -86,6 +86,7 @@ const startHandler = async (params, options) => {
86
86
  offthreadVideoCacheSizeInBytes: params.offthreadVideoCacheSizeInBytes,
87
87
  deleteAfter: params.deleteAfter,
88
88
  colorSpace: params.colorSpace,
89
+ preferLossless: params.preferLossless,
89
90
  };
90
91
  // Don't replace with callLambda(), we want to return before the render is snone
91
92
  await (0, aws_clients_1.getLambdaClient)((0, get_current_region_1.getCurrentRegionInFunction)()).send(new client_lambda_1.InvokeCommand({
@@ -43,7 +43,12 @@ const innerStillHandler = async ({ params: lambdaParams, expectedBucketOwner, re
43
43
  }
44
44
  (0, validate_download_behavior_1.validateDownloadBehavior)(lambdaParams.downloadBehavior);
45
45
  (0, validate_privacy_1.validatePrivacy)(lambdaParams.privacy, true);
46
- (0, validate_outname_1.validateOutname)(lambdaParams.outName, null, null);
46
+ (0, validate_outname_1.validateOutname)({
47
+ outName: lambdaParams.outName,
48
+ codec: null,
49
+ audioCodecSetting: null,
50
+ separateAudioTo: null,
51
+ });
47
52
  const start = Date.now();
48
53
  const browserInstancePromise = (0, get_browser_instance_1.getBrowserInstance)(lambdaParams.logLevel, false, lambdaParams.chromiumOptions);
49
54
  const bucketNamePromise = (_a = lambdaParams.bucketName) !== null && _a !== void 0 ? _a : (0, get_or_create_bucket_1.internalGetOrCreateBucket)({
@@ -112,8 +117,6 @@ const innerStillHandler = async ({ params: lambdaParams, expectedBucketOwner, re
112
117
  renderId,
113
118
  outName: (_c = lambdaParams.outName) !== null && _c !== void 0 ? _c : undefined,
114
119
  privacy: lambdaParams.privacy,
115
- everyNthFrame: 1,
116
- frameRange: [lambdaParams.frame, lambdaParams.frame],
117
120
  audioCodec: null,
118
121
  deleteAfter: lambdaParams.deleteAfter,
119
122
  numberOfGifLoops: null,
@@ -1,6 +1,6 @@
1
1
  export declare const LambdaInternals: {
2
2
  executeCommand: (args: string[], remotionRoot: string, logLevel: "verbose" | "info" | "warn" | "error") => Promise<void>;
3
- makeLambdaRenderMediaPayload: ({ rendererFunctionName, frameRange, framesPerLambda, forceBucketName: bucketName, codec, composition, serveUrl, imageFormat, inputProps, region, crf, envVariables, pixelFormat, proResProfile, x264Preset, maxRetries, privacy, logLevel, outName, timeoutInMilliseconds, chromiumOptions, scale, everyNthFrame, numberOfGifLoops, audioBitrate, concurrencyPerLambda, audioCodec, forceHeight, forceWidth, webhook, videoBitrate, encodingMaxRate, encodingBufferSize, downloadBehavior, muted, overwrite, jpegQuality, offthreadVideoCacheSizeInBytes, deleteAfter, colorSpace, }: import("./api/make-lambda-payload").InnerRenderMediaOnLambdaInput) => Promise<import("./defaults").LambdaStartPayload>;
3
+ makeLambdaRenderMediaPayload: ({ rendererFunctionName, frameRange, framesPerLambda, forceBucketName: bucketName, codec, composition, serveUrl, imageFormat, inputProps, region, crf, envVariables, pixelFormat, proResProfile, x264Preset, maxRetries, privacy, logLevel, outName, timeoutInMilliseconds, chromiumOptions, scale, everyNthFrame, numberOfGifLoops, audioBitrate, concurrencyPerLambda, audioCodec, forceHeight, forceWidth, webhook, videoBitrate, encodingMaxRate, encodingBufferSize, downloadBehavior, muted, overwrite, jpegQuality, offthreadVideoCacheSizeInBytes, deleteAfter, colorSpace, preferLossless, }: import("./api/make-lambda-payload").InnerRenderMediaOnLambdaInput) => Promise<import("./defaults").LambdaStartPayload>;
4
4
  getRenderProgressPayload: ({ bucketName, renderId, s3OutputProvider, logLevel, }: import(".").GetRenderProgressInput) => import("./defaults").LambdaStatusPayload;
5
5
  makeLambdaRenderStillPayload: ({ serveUrl, inputProps, imageFormat, envVariables, quality, jpegQuality, region, maxRetries, composition, privacy, frame, logLevel, outName, timeoutInMilliseconds, chromiumOptions, scale, downloadBehavior, forceHeight, forceWidth, forceBucketName, dumpBrowserLogs, offthreadVideoCacheSizeInBytes, deleteAfter, }: import(".").RenderStillOnLambdaInput) => Promise<{
6
6
  type: import("./defaults").LambdaRoutines.still;
@@ -52,9 +52,10 @@ export declare const lambdaTimingsKey: ({ renderId, chunk, start, rendered, }: {
52
52
  rendered: number;
53
53
  }) => string;
54
54
  export declare const chunkKey: (renderId: string) => string;
55
- export declare const chunkKeyForIndex: ({ renderId, index, }: {
55
+ export declare const chunkKeyForIndex: ({ renderId, index, type, }: {
56
56
  renderId: string;
57
57
  index: number;
58
+ type: 'video' | 'audio';
58
59
  }) => string;
59
60
  export declare const getErrorKeyPrefix: (renderId: string) => string;
60
61
  export declare const getErrorFileName: ({ renderId, chunk, attempt, }: {
@@ -157,6 +158,7 @@ export type LambdaStartPayload = {
157
158
  offthreadVideoCacheSizeInBytes: number | null;
158
159
  deleteAfter: DeleteAfter | null;
159
160
  colorSpace: ColorSpace;
161
+ preferLossless: boolean;
160
162
  };
161
163
  export type LambdaStatusPayload = {
162
164
  type: LambdaRoutines.status;
@@ -214,6 +216,7 @@ export type LambdaPayloads = {
214
216
  offthreadVideoCacheSizeInBytes: number | null;
215
217
  deleteAfter: DeleteAfter | null;
216
218
  colorSpace: ColorSpace;
219
+ preferLossless: boolean;
217
220
  };
218
221
  status: LambdaStatusPayload;
219
222
  renderer: {
@@ -255,6 +258,7 @@ export type LambdaPayloads = {
255
258
  launchFunctionConfig: {
256
259
  version: string;
257
260
  };
261
+ preferLossless: boolean;
258
262
  offthreadVideoCacheSizeInBytes: number | null;
259
263
  deleteAfter: DeleteAfter | null;
260
264
  colorSpace: ColorSpace;
@@ -304,6 +308,8 @@ export type LambdaPayloads = {
304
308
  inputProps: SerializedInputProps;
305
309
  serializedResolvedProps: SerializedInputProps;
306
310
  logLevel: LogLevel;
311
+ framesPerLambda: number;
312
+ preferLossless: boolean;
307
313
  };
308
314
  };
309
315
  export type LambdaPayload = LambdaPayloads[LambdaRoutines];
@@ -316,6 +322,9 @@ type Discriminated = {
316
322
  } | {
317
323
  type: 'video';
318
324
  imageFormat: VideoImageFormat;
325
+ muted: boolean;
326
+ frameRange: [number, number];
327
+ everyNthFrame: number;
319
328
  };
320
329
  export type RenderMetadata = Discriminated & {
321
330
  siteId: string;
@@ -335,8 +344,6 @@ export type RenderMetadata = Discriminated & {
335
344
  renderId: string;
336
345
  outName: OutNameInputWithoutCredentials | undefined;
337
346
  privacy: Privacy;
338
- frameRange: [number, number];
339
- everyNthFrame: number;
340
347
  deleteAfter: DeleteAfter | null;
341
348
  numberOfGifLoops: number | null;
342
349
  audioBitrate: string | null;
@@ -47,7 +47,7 @@ const lambdaTimingsKey = ({ renderId, chunk, start, rendered, }) => `${(0, expor
47
47
  exports.lambdaTimingsKey = lambdaTimingsKey;
48
48
  const chunkKey = (renderId) => `${(0, exports.rendersPrefix)(renderId)}/chunks/chunk`;
49
49
  exports.chunkKey = chunkKey;
50
- const chunkKeyForIndex = ({ renderId, index, }) => `${(0, exports.chunkKey)(renderId)}:${String(index).padStart(8, '0')}`;
50
+ const chunkKeyForIndex = ({ renderId, index, type, }) => `${(0, exports.chunkKey)(renderId)}:${String(index).padStart(8, '0')}:${type}`;
51
51
  exports.chunkKeyForIndex = chunkKeyForIndex;
52
52
  const getErrorKeyPrefix = (renderId) => `${(0, exports.rendersPrefix)(renderId)}/errors/`;
53
53
  exports.getErrorKeyPrefix = getErrorKeyPrefix;
@@ -1,4 +1,5 @@
1
1
  export declare const parseLambdaChunkKey: (key: string) => {
2
2
  renderId: string;
3
3
  chunk: number;
4
+ type: string;
4
5
  };
@@ -2,13 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseLambdaChunkKey = void 0;
4
4
  const parseLambdaChunkKey = (key) => {
5
- const match = key.match(/^renders\/(.*)\/chunks\/chunk:([0-9]+)$/);
5
+ const match = key.match(/^renders\/(.*)\/chunks\/chunk:([0-9]+):(video|audio)$/);
6
6
  if (!match) {
7
7
  throw new Error(`Cannot parse filename ${key} into timing information. Malformed data.`);
8
8
  }
9
9
  return {
10
10
  renderId: match[1],
11
11
  chunk: Number(match[2]),
12
+ type: match[3],
12
13
  };
13
14
  };
14
15
  exports.parseLambdaChunkKey = parseLambdaChunkKey;
@@ -1,3 +1,8 @@
1
1
  import type { AudioCodec, Codec } from '@remotion/renderer';
2
2
  import type { OutNameInputWithoutCredentials } from './constants';
3
- export declare const validateOutname: (outName: OutNameInputWithoutCredentials | undefined | null, codec: Codec | null, audioCodec: AudioCodec | null) => void;
3
+ export declare const validateOutname: ({ outName, codec, audioCodecSetting, separateAudioTo, }: {
4
+ outName: OutNameInputWithoutCredentials | undefined | null;
5
+ codec: Codec | null;
6
+ audioCodecSetting: AudioCodec | null;
7
+ separateAudioTo: string | null;
8
+ }) => void;
@@ -14,7 +14,7 @@ const validateS3Key = (s3Key) => {
14
14
  '. Check for invalid characters.');
15
15
  }
16
16
  };
17
- const validateOutname = (outName, codec, audioCodec) => {
17
+ const validateOutname = ({ outName, codec, audioCodecSetting, separateAudioTo, }) => {
18
18
  if (typeof outName === 'undefined' || outName === null) {
19
19
  return;
20
20
  }
@@ -26,9 +26,10 @@ const validateOutname = (outName, codec, audioCodec) => {
26
26
  if (codec) {
27
27
  pure_1.NoReactAPIs.validateOutputFilename({
28
28
  codec,
29
- audioCodec,
29
+ audioCodecSetting,
30
30
  extension: pure_1.NoReactAPIs.getExtensionOfFilename(outName),
31
31
  preferLossless: false,
32
+ separateAudioTo,
32
33
  });
33
34
  }
34
35
  validateS3Key(outName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/lambda",
3
- "version": "4.0.122",
3
+ "version": "4.0.124",
4
4
  "description": "Distributed renderer for Remotion based on AWS Lambda",
5
5
  "main": "dist/index.js",
6
6
  "sideEffects": false,
@@ -26,10 +26,10 @@
26
26
  "aws-policies": "^1.0.1",
27
27
  "mime-types": "2.1.34",
28
28
  "zod": "3.22.3",
29
- "@remotion/bundler": "4.0.122",
30
- "@remotion/cli": "4.0.122",
31
- "remotion": "4.0.122",
32
- "@remotion/renderer": "4.0.122"
29
+ "@remotion/bundler": "4.0.124",
30
+ "@remotion/renderer": "4.0.124",
31
+ "@remotion/cli": "4.0.124",
32
+ "remotion": "4.0.124"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@jonny/eslint-config": "3.0.276",
@@ -43,11 +43,11 @@
43
43
  "ts-node": "^10.8.0",
44
44
  "vitest": "0.31.1",
45
45
  "zip-lib": "^0.7.2",
46
- "@remotion/bundler": "4.0.122",
47
- "@remotion/compositor-linux-arm64-gnu": "4.0.122"
46
+ "@remotion/bundler": "4.0.124",
47
+ "@remotion/compositor-linux-arm64-gnu": "4.0.124"
48
48
  },
49
49
  "peerDependencies": {
50
- "@remotion/bundler": "4.0.122"
50
+ "@remotion/bundler": "4.0.124"
51
51
  },
52
52
  "publishConfig": {
53
53
  "access": "public"
Binary file