@remotion/lambda 4.0.0-audio-mixing.5 → 4.0.0-audio.10

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.
@@ -70,7 +70,6 @@ const createFunction = async ({ createCloudWatchLogGroup, region, zipFile, funct
70
70
  }));
71
71
  }
72
72
  catch (err) {
73
- console.log(err);
74
73
  console.warn('⚠️ Could not lock the runtime version. We recommend to update your policies to prevent your functions from breaking soon: https://remotion.dev/docs/lambda/feb-2023-incident');
75
74
  }
76
75
  return { FunctionName: FunctionName };
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.deploySite = void 0;
4
- const fs_1 = require("fs");
7
+ const fs_1 = __importDefault(require("fs"));
5
8
  const io_1 = require("../functions/helpers/io");
6
9
  const bundle_site_1 = require("../shared/bundle-site");
7
10
  const constants_1 = require("../shared/constants");
@@ -84,7 +87,14 @@ const deploySite = async ({ bucketName, entryPoint, siteName, options, region, }
84
87
  })),
85
88
  ]);
86
89
  if (!process.env.VITEST) {
87
- (0, fs_1.rmdirSync)(bundled, { recursive: true });
90
+ if (fs_1.default.rmSync) {
91
+ fs_1.default.rmSync(bundled, {
92
+ recursive: true,
93
+ });
94
+ }
95
+ else {
96
+ fs_1.default.rmdirSync(bundled, { recursive: true });
97
+ }
88
98
  }
89
99
  return {
90
100
  serveUrl: (0, make_s3_url_1.makeS3ServeUrl)({ bucketName, subFolder, region }),
@@ -11,6 +11,7 @@ export declare type GetCompositionsOnLambdaInput = {
11
11
  logLevel?: LogLevel;
12
12
  timeoutInMilliseconds?: number;
13
13
  forceBucketName?: string;
14
+ dumpBrowserLogs?: boolean;
14
15
  };
15
16
  export declare type GetCompositionsOnLambdaOutput = TCompMetadata[];
16
17
  /**
@@ -24,6 +25,7 @@ export declare type GetCompositionsOnLambdaOutput = TCompMetadata[];
24
25
  * @param params.logLevel The log level of the Lambda function
25
26
  * @param params.timeoutInMilliseconds The timeout of the Lambda function
26
27
  * @param params.chromiumOptions The options to pass to Chromium
28
+ * @param params.dumpBrowserLogs Whether to print browser logs to CloudWatch
27
29
  * @returns The compositions
28
30
  */
29
- export declare const getCompositionsOnLambda: ({ chromiumOptions, serveUrl, region, inputProps, functionName, envVariables, logLevel, timeoutInMilliseconds, forceBucketName: bucketName, }: GetCompositionsOnLambdaInput) => Promise<GetCompositionsOnLambdaOutput>;
31
+ export declare const getCompositionsOnLambda: ({ chromiumOptions, serveUrl, region, inputProps, functionName, envVariables, logLevel, timeoutInMilliseconds, forceBucketName: bucketName, dumpBrowserLogs, }: GetCompositionsOnLambdaInput) => Promise<GetCompositionsOnLambdaOutput>;
@@ -16,9 +16,10 @@ const serialize_input_props_1 = require("../shared/serialize-input-props");
16
16
  * @param params.logLevel The log level of the Lambda function
17
17
  * @param params.timeoutInMilliseconds The timeout of the Lambda function
18
18
  * @param params.chromiumOptions The options to pass to Chromium
19
+ * @param params.dumpBrowserLogs Whether to print browser logs to CloudWatch
19
20
  * @returns The compositions
20
21
  */
21
- const getCompositionsOnLambda = async ({ chromiumOptions, serveUrl, region, inputProps, functionName, envVariables, logLevel, timeoutInMilliseconds, forceBucketName: bucketName, }) => {
22
+ const getCompositionsOnLambda = async ({ chromiumOptions, serveUrl, region, inputProps, functionName, envVariables, logLevel, timeoutInMilliseconds, forceBucketName: bucketName, dumpBrowserLogs, }) => {
22
23
  var _a;
23
24
  const serializedInputProps = await (0, serialize_input_props_1.serializeInputProps)({
24
25
  inputProps,
@@ -39,6 +40,7 @@ const getCompositionsOnLambda = async ({ chromiumOptions, serveUrl, region, inpu
39
40
  timeoutInMilliseconds: timeoutInMilliseconds !== null && timeoutInMilliseconds !== void 0 ? timeoutInMilliseconds : 30000,
40
41
  version: version_1.VERSION,
41
42
  bucketName: bucketName !== null && bucketName !== void 0 ? bucketName : null,
43
+ dumpBrowserLogs: dumpBrowserLogs !== null && dumpBrowserLogs !== void 0 ? dumpBrowserLogs : false,
42
44
  },
43
45
  region,
44
46
  });
@@ -5,6 +5,7 @@ export declare type GetOrCreateBucketInput = {
5
5
  };
6
6
  export declare type GetOrCreateBucketOutput = {
7
7
  bucketName: string;
8
+ alreadyExisted: boolean;
8
9
  };
9
10
  /**
10
11
  * @description Creates a bucket for Remotion Lambda in your S3 account. If one already exists, it will get returned instead.
@@ -19,7 +19,7 @@ const getOrCreateBucket = async (options) => {
19
19
  }
20
20
  if (remotionBuckets.length === 1) {
21
21
  (_a = options.onBucketEnsured) === null || _a === void 0 ? void 0 : _a.call(options);
22
- return { bucketName: remotionBuckets[0].name };
22
+ return { bucketName: remotionBuckets[0].name, alreadyExisted: true };
23
23
  }
24
24
  const bucketName = (0, validate_bucketname_1.makeBucketName)(options.region);
25
25
  await (0, create_bucket_1.createBucket)({
@@ -27,6 +27,6 @@ const getOrCreateBucket = async (options) => {
27
27
  region: options.region,
28
28
  });
29
29
  (_b = options.onBucketEnsured) === null || _b === void 0 ? void 0 : _b.call(options);
30
- return { bucketName };
30
+ return { bucketName, alreadyExisted: false };
31
31
  };
32
32
  exports.getOrCreateBucket = getOrCreateBucket;
@@ -42,6 +42,7 @@ export declare type RenderMediaOnLambdaInput = {
42
42
  rendererFunctionName?: string | null;
43
43
  forceBucketName?: string;
44
44
  audioCodec?: AudioCodec | null;
45
+ dumpBrowserLogs?: boolean;
45
46
  };
46
47
  export declare type RenderMediaOnLambdaOutput = {
47
48
  renderId: string;
@@ -66,10 +67,11 @@ export declare type RenderMediaOnLambdaOutput = {
66
67
  * @param params.maxRetries How often rendering a chunk may fail before the media render gets aborted. Default "1"
67
68
  * @param params.logLevel Level of logging that Lambda function should perform. Default "info".
68
69
  * @param params.webhook Configuration for webhook called upon completion or timeout of the render.
70
+ * @param params.dumpBrowserLogs Whether to print browser logs to CloudWatch
69
71
  * @returns {Promise<RenderMediaOnLambdaOutput>} See documentation for detailed structure
70
72
  */
71
- export declare const renderMediaOnLambda: ({ functionName, serveUrl, inputProps, codec, imageFormat, crf, envVariables, pixelFormat, proResProfile, quality, region, maxRetries, composition, framesPerLambda, privacy, logLevel, frameRange, outName, timeoutInMilliseconds, chromiumOptions, scale, numberOfGifLoops, everyNthFrame, concurrencyPerLambda, downloadBehavior, muted, overwrite, audioBitrate, videoBitrate, webhook, forceHeight, forceWidth, rendererFunctionName, forceBucketName: bucketName, audioCodec, }: RenderMediaOnLambdaInput) => Promise<RenderMediaOnLambdaOutput>;
73
+ export declare const renderMediaOnLambda: ({ functionName, serveUrl, inputProps, codec, imageFormat, crf, envVariables, pixelFormat, proResProfile, quality, region, maxRetries, composition, framesPerLambda, privacy, logLevel, frameRange, outName, timeoutInMilliseconds, chromiumOptions, scale, numberOfGifLoops, everyNthFrame, concurrencyPerLambda, downloadBehavior, muted, overwrite, audioBitrate, videoBitrate, webhook, forceHeight, forceWidth, rendererFunctionName, forceBucketName: bucketName, audioCodec, dumpBrowserLogs, }: RenderMediaOnLambdaInput) => Promise<RenderMediaOnLambdaOutput>;
72
74
  /**
73
75
  * @deprecated Renamed to renderMediaOnLambda()
74
76
  */
75
- export declare const renderVideoOnLambda: ({ functionName, serveUrl, inputProps, codec, imageFormat, crf, envVariables, pixelFormat, proResProfile, quality, region, maxRetries, composition, framesPerLambda, privacy, logLevel, frameRange, outName, timeoutInMilliseconds, chromiumOptions, scale, numberOfGifLoops, everyNthFrame, concurrencyPerLambda, downloadBehavior, muted, overwrite, audioBitrate, videoBitrate, webhook, forceHeight, forceWidth, rendererFunctionName, forceBucketName: bucketName, audioCodec, }: RenderMediaOnLambdaInput) => Promise<RenderMediaOnLambdaOutput>;
77
+ export declare const renderVideoOnLambda: ({ functionName, serveUrl, inputProps, codec, imageFormat, crf, envVariables, pixelFormat, proResProfile, quality, region, maxRetries, composition, framesPerLambda, privacy, logLevel, frameRange, outName, timeoutInMilliseconds, chromiumOptions, scale, numberOfGifLoops, everyNthFrame, concurrencyPerLambda, downloadBehavior, muted, overwrite, audioBitrate, videoBitrate, webhook, forceHeight, forceWidth, rendererFunctionName, forceBucketName: bucketName, audioCodec, dumpBrowserLogs, }: RenderMediaOnLambdaInput) => Promise<RenderMediaOnLambdaOutput>;
@@ -27,9 +27,10 @@ const validate_serveurl_1 = require("../shared/validate-serveurl");
27
27
  * @param params.maxRetries How often rendering a chunk may fail before the media render gets aborted. Default "1"
28
28
  * @param params.logLevel Level of logging that Lambda function should perform. Default "info".
29
29
  * @param params.webhook Configuration for webhook called upon completion or timeout of the render.
30
+ * @param params.dumpBrowserLogs Whether to print browser logs to CloudWatch
30
31
  * @returns {Promise<RenderMediaOnLambdaOutput>} See documentation for detailed structure
31
32
  */
32
- const renderMediaOnLambda = async ({ functionName, serveUrl, inputProps, codec, imageFormat, crf, envVariables, pixelFormat, proResProfile, quality, region, maxRetries, composition, framesPerLambda, privacy, logLevel, frameRange, outName, timeoutInMilliseconds, chromiumOptions, scale, numberOfGifLoops, everyNthFrame, concurrencyPerLambda, downloadBehavior, muted, overwrite, audioBitrate, videoBitrate, webhook, forceHeight, forceWidth, rendererFunctionName, forceBucketName: bucketName, audioCodec, }) => {
33
+ const renderMediaOnLambda = async ({ functionName, serveUrl, inputProps, codec, imageFormat, crf, envVariables, pixelFormat, proResProfile, quality, region, maxRetries, composition, framesPerLambda, privacy, logLevel, frameRange, outName, timeoutInMilliseconds, chromiumOptions, scale, numberOfGifLoops, everyNthFrame, concurrencyPerLambda, downloadBehavior, muted, overwrite, audioBitrate, videoBitrate, webhook, forceHeight, forceWidth, rendererFunctionName, forceBucketName: bucketName, audioCodec, dumpBrowserLogs, }) => {
33
34
  var _a;
34
35
  const actualCodec = (0, validate_lambda_codec_1.validateLambdaCodec)(codec);
35
36
  (0, validate_serveurl_1.validateServeUrl)(serveUrl);
@@ -83,6 +84,7 @@ const renderMediaOnLambda = async ({ functionName, serveUrl, inputProps, codec,
83
84
  forceWidth: forceWidth !== null && forceWidth !== void 0 ? forceWidth : null,
84
85
  bucketName: bucketName !== null && bucketName !== void 0 ? bucketName : null,
85
86
  audioCodec: audioCodec !== null && audioCodec !== void 0 ? audioCodec : null,
87
+ dumpBrowserLogs: dumpBrowserLogs !== null && dumpBrowserLogs !== void 0 ? dumpBrowserLogs : false,
86
88
  },
87
89
  region,
88
90
  });
@@ -23,6 +23,7 @@ export declare type RenderStillOnLambdaInput = {
23
23
  forceWidth?: number | null;
24
24
  forceHeight?: number | null;
25
25
  forceBucketName?: string;
26
+ dumpBrowserLogs?: boolean;
26
27
  };
27
28
  export declare type RenderStillOnLambdaOutput = {
28
29
  estimatedPrice: CostsInfo;
@@ -46,6 +47,7 @@ export declare type RenderStillOnLambdaOutput = {
46
47
  * @param params.maxRetries How often rendering a chunk may fail before the video render gets aborted.
47
48
  * @param params.frame Which frame should be used for the still image. Default 0.
48
49
  * @param params.privacy Whether the item in the S3 bucket should be public. Possible values: `"private"` and `"public"`
50
+ * @param params.dumpBrowserLogs Whether to print browser logs to CloudWatch.
49
51
  * @returns {Promise<RenderStillOnLambdaOutput>} See documentation for exact response structure.
50
52
  */
51
- export declare const renderStillOnLambda: ({ functionName, serveUrl, inputProps, imageFormat, envVariables, quality, region, maxRetries, composition, privacy, frame, logLevel, outName, timeoutInMilliseconds, chromiumOptions, scale, downloadBehavior, forceHeight, forceWidth, forceBucketName, }: RenderStillOnLambdaInput) => Promise<RenderStillOnLambdaOutput>;
53
+ export declare const renderStillOnLambda: ({ functionName, serveUrl, inputProps, imageFormat, envVariables, quality, region, maxRetries, composition, privacy, frame, logLevel, outName, timeoutInMilliseconds, chromiumOptions, scale, downloadBehavior, forceHeight, forceWidth, forceBucketName, dumpBrowserLogs, }: RenderStillOnLambdaInput) => Promise<RenderStillOnLambdaOutput>;
@@ -20,9 +20,10 @@ const serialize_input_props_1 = require("../shared/serialize-input-props");
20
20
  * @param params.maxRetries How often rendering a chunk may fail before the video render gets aborted.
21
21
  * @param params.frame Which frame should be used for the still image. Default 0.
22
22
  * @param params.privacy Whether the item in the S3 bucket should be public. Possible values: `"private"` and `"public"`
23
+ * @param params.dumpBrowserLogs Whether to print browser logs to CloudWatch.
23
24
  * @returns {Promise<RenderStillOnLambdaOutput>} See documentation for exact response structure.
24
25
  */
25
- const renderStillOnLambda = async ({ functionName, serveUrl, inputProps, imageFormat, envVariables, quality, region, maxRetries, composition, privacy, frame, logLevel, outName, timeoutInMilliseconds, chromiumOptions, scale, downloadBehavior, forceHeight, forceWidth, forceBucketName, }) => {
26
+ const renderStillOnLambda = async ({ functionName, serveUrl, inputProps, imageFormat, envVariables, quality, region, maxRetries, composition, privacy, frame, logLevel, outName, timeoutInMilliseconds, chromiumOptions, scale, downloadBehavior, forceHeight, forceWidth, forceBucketName, dumpBrowserLogs, }) => {
26
27
  var _a;
27
28
  const serializedInputProps = await (0, serialize_input_props_1.serializeInputProps)({
28
29
  inputProps,
@@ -55,6 +56,7 @@ const renderStillOnLambda = async ({ functionName, serveUrl, inputProps, imageFo
55
56
  forceHeight: forceHeight !== null && forceHeight !== void 0 ? forceHeight : null,
56
57
  forceWidth: forceWidth !== null && forceWidth !== void 0 ? forceWidth : null,
57
58
  bucketName: forceBucketName !== null && forceBucketName !== void 0 ? forceBucketName : null,
59
+ dumpBrowserLogs: dumpBrowserLogs !== null && dumpBrowserLogs !== void 0 ? dumpBrowserLogs : false,
58
60
  },
59
61
  region,
60
62
  });
@@ -0,0 +1,14 @@
1
+ export declare type SpeculateFunctionNameInput = {
2
+ memorySizeInMb: string | number;
3
+ diskSizeInMb: string | number;
4
+ timeoutInSeconds: string | number;
5
+ };
6
+ /**
7
+ * @description Speculate the name of a lambda function that will be created when you call `deployFunction`, based on the function configuration.
8
+ * @see [Documentation](https://www.remotion.dev/docs/lambda/speculatefunctionname)
9
+ * @param options.memorySizeInMb How much memory is allocated to the Lambda function.
10
+ * @param options.diskSizeInMb The amount of storage the function is allocated.
11
+ * @param options.timeoutInSeconds Time in seconds until the function times out.
12
+ * @returns {string} The speculated lambda function name
13
+ */
14
+ export declare const speculateFunctionName: ({ memorySizeInMb, diskSizeInMb, timeoutInSeconds, }: SpeculateFunctionNameInput) => string;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.speculateFunctionName = void 0;
4
+ const defaults_1 = require("../defaults");
5
+ const lambda_version_string_1 = require("../shared/lambda-version-string");
6
+ /**
7
+ * @description Speculate the name of a lambda function that will be created when you call `deployFunction`, based on the function configuration.
8
+ * @see [Documentation](https://www.remotion.dev/docs/lambda/speculatefunctionname)
9
+ * @param options.memorySizeInMb How much memory is allocated to the Lambda function.
10
+ * @param options.diskSizeInMb The amount of storage the function is allocated.
11
+ * @param options.timeoutInSeconds Time in seconds until the function times out.
12
+ * @returns {string} The speculated lambda function name
13
+ */
14
+ const speculateFunctionName = ({ memorySizeInMb, diskSizeInMb, timeoutInSeconds, }) => {
15
+ return [
16
+ `${defaults_1.RENDER_FN_PREFIX}${lambda_version_string_1.LAMBDA_VERSION_STRING}`,
17
+ `mem${memorySizeInMb}mb`,
18
+ `disk${diskSizeInMb}mb`,
19
+ `${timeoutInSeconds}sec`,
20
+ ].join('-');
21
+ };
22
+ exports.speculateFunctionName = speculateFunctionName;
@@ -2,15 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.quotasIncreaseCommand = exports.INCREASE_SUBCOMMAND = void 0;
4
4
  const client_service_quotas_1 = require("@aws-sdk/client-service-quotas");
5
- const cli_1 = require("@remotion/cli");
6
5
  const process_1 = require("process");
7
6
  const _1 = require(".");
8
7
  const defaults_1 = require("../../../defaults");
9
8
  const aws_clients_1 = require("../../../shared/aws-clients");
9
+ const args_1 = require("../../args");
10
10
  const get_aws_region_1 = require("../../get-aws-region");
11
11
  const confirm_1 = require("../../helpers/confirm");
12
12
  const quit_1 = require("../../helpers/quit");
13
+ const log_1 = require("../../log");
13
14
  exports.INCREASE_SUBCOMMAND = 'increase';
15
+ const makeQuotaUrl = ({ region, quotaId, }) => {
16
+ return `https://${region}.console.aws.amazon.com/servicequotas/home/services/lambda/quotas/${quotaId}`;
17
+ };
14
18
  const quotasIncreaseCommand = async () => {
15
19
  var _a, _b, _c;
16
20
  const region = (0, get_aws_region_1.getAwsRegion)();
@@ -30,28 +34,44 @@ const quotasIncreaseCommand = async () => {
30
34
  ]);
31
35
  const openCase = (_a = changes.RequestedQuotas) === null || _a === void 0 ? void 0 : _a.find((r) => r.Status === 'CASE_OPENED');
32
36
  if (openCase) {
33
- cli_1.CliInternals.Log.warn(`A request to increase it to ${openCase.DesiredValue} is already pending:`);
34
- cli_1.CliInternals.Log.warn(`https://${region}.console.aws.amazon.com/support/home#/case/?displayId=${openCase.CaseId}`);
37
+ log_1.Log.warn(`A request to increase it to ${openCase.DesiredValue} is already pending:`);
38
+ log_1.Log.warn(`https://${region}.console.aws.amazon.com/support/home#/case/?displayId=${openCase.CaseId}`);
35
39
  (0, process_1.exit)(1);
36
40
  }
37
41
  const concurrencyCurrent = (_b = concurrencyLimit.Quota) === null || _b === void 0 ? void 0 : _b.Value;
38
42
  const defaultConcurrency = (_c = defaultConcurrencyLimit.Quota) === null || _c === void 0 ? void 0 : _c.Value;
39
43
  const increaseRecommended = concurrencyCurrent <= defaultConcurrency;
40
- if (!increaseRecommended) {
41
- cli_1.CliInternals.Log.info(`Current limit of ${concurrencyCurrent} is already increased over the default (${defaultConcurrency}). Increase it further via the AWS console.`);
44
+ if (!increaseRecommended && !args_1.forceFlagProvided) {
45
+ log_1.Log.error(`Current limit of ${concurrencyCurrent} is already increased over the default (${defaultConcurrency}).`);
46
+ log_1.Log.info('You can force the increase with the --force flag.');
47
+ log_1.Log.info('You are more likely to get an increase if you attach a reason. Go so by going to the AWS console:');
48
+ log_1.Log.info(makeQuotaUrl({ quotaId: defaults_1.LAMBDA_CONCURRENCY_LIMIT_QUOTA, region }));
42
49
  (0, quit_1.quit)(1);
43
50
  }
44
51
  const newLimit = Math.floor(concurrencyCurrent / 5000) * 5000 + 5000;
45
- cli_1.CliInternals.Log.info(`Sending request to AWS to increase concurrency limit from ${concurrencyCurrent} to ${newLimit}.`);
52
+ log_1.Log.info(`Sending request to AWS to increase concurrency limit from ${concurrencyCurrent} to ${newLimit}.`);
46
53
  await (0, confirm_1.confirmCli)({
47
54
  allowForceFlag: true,
48
55
  delMessage: 'Send? (Y/n)',
49
56
  });
50
- await (0, aws_clients_1.getServiceQuotasClient)(region).send(new client_service_quotas_1.RequestServiceQuotaIncreaseCommand({
51
- QuotaCode: defaults_1.LAMBDA_CONCURRENCY_LIMIT_QUOTA,
52
- DesiredValue: newLimit,
53
- ServiceCode: 'lambda',
54
- }));
55
- cli_1.CliInternals.Log.info(`Requested increase successfully. Run "${defaults_1.BINARY_NAME} ${_1.QUOTAS_COMMAND}" to check whether your request was approved.`);
57
+ try {
58
+ await (0, aws_clients_1.getServiceQuotasClient)(region).send(new client_service_quotas_1.RequestServiceQuotaIncreaseCommand({
59
+ QuotaCode: defaults_1.LAMBDA_CONCURRENCY_LIMIT_QUOTA,
60
+ DesiredValue: newLimit,
61
+ ServiceCode: 'lambda',
62
+ }));
63
+ }
64
+ catch (err) {
65
+ if (err.name === 'DependencyAccessDeniedException') {
66
+ log_1.Log.error('Could not request increase because this is a sub-account of another AWS account.');
67
+ log_1.Log.error(`Please go to ${makeQuotaUrl({
68
+ quotaId: defaults_1.LAMBDA_CONCURRENCY_LIMIT_QUOTA,
69
+ region,
70
+ })} to request the increase via the AWS console.`);
71
+ return;
72
+ }
73
+ throw err;
74
+ }
75
+ log_1.Log.info(`Requested increase successfully. Run "${defaults_1.BINARY_NAME} ${_1.QUOTAS_COMMAND}" to check whether your request was approved.`);
56
76
  };
57
77
  exports.quotasIncreaseCommand = quotasIncreaseCommand;
package/dist/client.d.ts CHANGED
@@ -6,9 +6,11 @@ import type { PresignUrlInput } from './api/presign-url';
6
6
  import { presignUrl } from './api/presign-url';
7
7
  import { renderMediaOnLambda, renderVideoOnLambda } from './api/render-media-on-lambda';
8
8
  import { renderStillOnLambda } from './api/render-still-on-lambda';
9
+ import type { SpeculateFunctionNameInput } from './api/speculate-function-name';
10
+ import { speculateFunctionName } from './api/speculate-function-name';
9
11
  import { validateWebhookSignature } from './api/validate-webhook-signature';
10
12
  import type { AwsRegion } from './pricing/aws-regions';
11
13
  import type { RenderProgress } from './shared/constants';
12
14
  import type { WebhookPayload } from './shared/invoke-webhook';
13
- export { renderVideoOnLambda, renderMediaOnLambda, renderStillOnLambda, getRenderProgress, getFunctions, validateWebhookSignature, getCompositionsOnLambda, presignUrl, getSites, };
14
- export type { AwsRegion, RenderProgress, WebhookPayload, PresignUrlInput };
15
+ export { renderVideoOnLambda, renderMediaOnLambda, renderStillOnLambda, getRenderProgress, getFunctions, validateWebhookSignature, getCompositionsOnLambda, presignUrl, getSites, speculateFunctionName, };
16
+ export type { AwsRegion, RenderProgress, SpeculateFunctionNameInput, WebhookPayload, PresignUrlInput, };
package/dist/client.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getSites = exports.presignUrl = exports.getCompositionsOnLambda = exports.validateWebhookSignature = exports.getFunctions = exports.getRenderProgress = exports.renderStillOnLambda = exports.renderMediaOnLambda = exports.renderVideoOnLambda = void 0;
3
+ exports.speculateFunctionName = exports.getSites = exports.presignUrl = exports.getCompositionsOnLambda = exports.validateWebhookSignature = exports.getFunctions = exports.getRenderProgress = exports.renderStillOnLambda = exports.renderMediaOnLambda = exports.renderVideoOnLambda = void 0;
4
4
  const get_compositions_on_lambda_1 = require("./api/get-compositions-on-lambda");
5
5
  Object.defineProperty(exports, "getCompositionsOnLambda", { enumerable: true, get: function () { return get_compositions_on_lambda_1.getCompositionsOnLambda; } });
6
6
  const get_functions_1 = require("./api/get-functions");
@@ -16,5 +16,7 @@ Object.defineProperty(exports, "renderMediaOnLambda", { enumerable: true, get: f
16
16
  Object.defineProperty(exports, "renderVideoOnLambda", { enumerable: true, get: function () { return render_media_on_lambda_1.renderVideoOnLambda; } });
17
17
  const render_still_on_lambda_1 = require("./api/render-still-on-lambda");
18
18
  Object.defineProperty(exports, "renderStillOnLambda", { enumerable: true, get: function () { return render_still_on_lambda_1.renderStillOnLambda; } });
19
+ const speculate_function_name_1 = require("./api/speculate-function-name");
20
+ Object.defineProperty(exports, "speculateFunctionName", { enumerable: true, get: function () { return speculate_function_name_1.speculateFunctionName; } });
19
21
  const validate_webhook_signature_1 = require("./api/validate-webhook-signature");
20
22
  Object.defineProperty(exports, "validateWebhookSignature", { enumerable: true, get: function () { return validate_webhook_signature_1.validateWebhookSignature; } });
@@ -9,7 +9,7 @@ const planFrameRanges = ({ framesPerLambda, frameRange, everyNthFrame, }) => {
9
9
  return {
10
10
  chunks: new Array(chunkCount).fill(1).map((_, i) => {
11
11
  const start = i * framesPerLambda * everyNthFrame + firstFrame;
12
- const end = Math.min(framesToRender[framesToRender.length - 1], (i + 1) * framesPerLambda * everyNthFrame - 1) + firstFrame;
12
+ const end = Math.min(framesToRender[framesToRender.length - 1], (i + 1) * framesPerLambda * everyNthFrame - 1 + firstFrame);
13
13
  return [start, end];
14
14
  }),
15
15
  };
@@ -10,7 +10,7 @@ const deserialize_input_props_1 = require("../shared/deserialize-input-props");
10
10
  const get_browser_instance_1 = require("./helpers/get-browser-instance");
11
11
  const get_current_region_1 = require("./helpers/get-current-region");
12
12
  const compositionsHandler = async (lambdaParams, options) => {
13
- var _a, _b;
13
+ var _a, _b, _c;
14
14
  if (lambdaParams.type !== defaults_1.LambdaRoutines.compositions) {
15
15
  throw new TypeError('Expected info compositions');
16
16
  }
@@ -25,7 +25,7 @@ const compositionsHandler = async (lambdaParams, options) => {
25
25
  (_a = lambdaParams.bucketName) !== null && _a !== void 0 ? _a : (0, get_or_create_bucket_1.getOrCreateBucket)({
26
26
  region,
27
27
  }).then((b) => b.bucketName),
28
- (0, get_browser_instance_1.getBrowserInstance)(renderer_1.RenderInternals.isEqualOrBelowLogLevel(lambdaParams.logLevel, 'verbose'), (_b = lambdaParams.chromiumOptions) !== null && _b !== void 0 ? _b : {}),
28
+ (0, get_browser_instance_1.getBrowserInstance)((_b = lambdaParams.dumpBrowserLogs) !== null && _b !== void 0 ? _b : renderer_1.RenderInternals.isEqualOrBelowLogLevel(lambdaParams.logLevel, 'verbose'), (_c = lambdaParams.chromiumOptions) !== null && _c !== void 0 ? _c : {}),
29
29
  ]);
30
30
  const inputProps = await (0, deserialize_input_props_1.deserializeInputProps)({
31
31
  bucketName,
@@ -1 +1 @@
1
- export declare const getCurrentRegionInFunction: () => "eu-central-1" | "eu-west-1" | "eu-west-2" | "eu-west-3" | "eu-south-1" | "eu-north-1" | "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "af-south-1" | "ap-south-1" | "ap-east-1" | "ap-southeast-1" | "ap-southeast-2" | "ap-northeast-1" | "ap-northeast-2" | "ap-northeast-3" | "ca-central-1" | "me-south-1" | "sa-east-1";
1
+ export declare const getCurrentRegionInFunction: () => "eu-central-1" | "eu-west-1" | "eu-west-2" | "eu-west-3" | "eu-north-1" | "us-east-1" | "us-east-2" | "us-west-1" | "us-west-2" | "ap-south-1" | "ap-southeast-1" | "ap-southeast-2" | "ap-northeast-1" | "ap-northeast-2" | "ap-northeast-3" | "ca-central-1" | "sa-east-1" | "eu-south-1" | "af-south-1" | "ap-east-1" | "me-south-1";
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.makeTimeoutError = void 0;
4
- const docs_url_1 = require("../../shared/docs-url");
5
4
  const parse_chunk_key_1 = require("../../shared/parse-chunk-key");
5
+ const make_timeout_message_1 = require("./make-timeout-message");
6
6
  const makeTimeoutError = ({ timeoutInMilliseconds, chunks, renderMetadata, }) => {
7
7
  const availableChunks = chunks.map((c) => (0, parse_chunk_key_1.parseLambdaChunkKey)(c.Key));
8
8
  const missingChunks = new Array(renderMetadata.totalChunks)
@@ -11,24 +11,11 @@ const makeTimeoutError = ({ timeoutInMilliseconds, chunks, renderMetadata, }) =>
11
11
  return !availableChunks.find((c) => c.chunk === i);
12
12
  })
13
13
  .map((_, i) => i);
14
- const missingChunksMessageList = missingChunks
15
- .map((ch) => {
16
- const isLastChunk = ch === renderMetadata.totalChunks - 1;
17
- const start = ch * renderMetadata.framesPerLambda;
18
- const end = isLastChunk
19
- ? renderMetadata.frameRange[1]
20
- : (ch + 1) * renderMetadata.framesPerLambda - 1;
21
- return `Chunk ${ch} (Frames ${start} - ${end})`;
22
- })
23
- .slice(0, 5)
24
- .join(', ');
25
- const message = [
26
- `The main function timed out after ${timeoutInMilliseconds}ms.`,
27
- `Consider increasing the timeout of your function.`,
28
- `The following chunks are missing (showing up to 5): ${missingChunksMessageList}.`,
29
- `You can use the "--timeout" parameter when deploying a function via CLI, or the "timeoutInSeconds" parameter when using the deployFunction() API.`,
30
- `${docs_url_1.DOCS_URL}/docs/lambda/cli/functions#deploy`,
31
- ].join('\n');
14
+ const message = (0, make_timeout_message_1.makeTimeoutMessage)({
15
+ missingChunks,
16
+ renderMetadata,
17
+ timeoutInMilliseconds,
18
+ });
32
19
  return {
33
20
  attempt: 1,
34
21
  chunk: null,
@@ -0,0 +1,6 @@
1
+ import type { RenderMetadata } from '../../defaults';
2
+ export declare const makeTimeoutMessage: ({ timeoutInMilliseconds, missingChunks, renderMetadata, }: {
3
+ timeoutInMilliseconds: number;
4
+ missingChunks: number[];
5
+ renderMetadata: RenderMetadata;
6
+ }) => string;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeTimeoutMessage = void 0;
4
+ const docs_url_1 = require("../../shared/docs-url");
5
+ const makeChunkMissingMessage = ({ missingChunks, renderMetadata, }) => {
6
+ const missingChunksMessageList = missingChunks
7
+ .map((ch) => {
8
+ const isLastChunk = ch === renderMetadata.totalChunks - 1;
9
+ const start = ch * renderMetadata.framesPerLambda;
10
+ const end = isLastChunk
11
+ ? renderMetadata.frameRange[1]
12
+ : (ch + 1) * renderMetadata.framesPerLambda - 1;
13
+ return `Chunk ${ch} (Frames ${start} - ${end})`;
14
+ })
15
+ .slice(0, 5);
16
+ if (missingChunksMessageList.length === 0) {
17
+ return 'All chunks have been successfully rendered, but the main function has timed out.';
18
+ }
19
+ return `The following chunks are missing (showing ${missingChunksMessageList.length} out of ${missingChunks.length}): ${missingChunksMessageList.join(', ')}.`;
20
+ };
21
+ const makeTimeoutMessage = ({ timeoutInMilliseconds, missingChunks, renderMetadata, }) => {
22
+ const message = [
23
+ `The main function timed out after ${timeoutInMilliseconds}ms.`,
24
+ `Consider increasing the timeout of your function.`,
25
+ makeChunkMissingMessage({ missingChunks, renderMetadata }),
26
+ `You can use the "--timeout" parameter when deploying a function via CLI, or the "timeoutInSeconds" parameter when using the deployFunction() API.`,
27
+ `${docs_url_1.DOCS_URL}/docs/lambda/cli/functions#deploy`,
28
+ ].join('\n');
29
+ return message;
30
+ };
31
+ exports.makeTimeoutMessage = makeTimeoutMessage;
@@ -80,6 +80,7 @@ const callFunctionWithRetry = async ({ payload, retries, functionName, }) => {
80
80
  functionName,
81
81
  });
82
82
  }
83
+ throw err;
83
84
  }
84
85
  };
85
86
  const innerLaunchHandler = async (params, options) => {
@@ -227,9 +228,11 @@ const innerLaunchHandler = async (params, options) => {
227
228
  launchFunctionConfig: {
228
229
  version: version_1.VERSION,
229
230
  },
231
+ dumpBrowserLogs: params.dumpBrowserLogs,
230
232
  };
231
233
  return payload;
232
234
  });
235
+ console.log('Render plan: ', chunks.map((c, i) => `Chunk ${i} (Frames ${c[0]} - ${c[1]})`).join(', '));
233
236
  const renderMetadata = {
234
237
  startedDate,
235
238
  videoConfig: comp,
@@ -292,10 +295,8 @@ const innerLaunchHandler = async (params, options) => {
292
295
  downloadBehavior: null,
293
296
  customCredentials: null,
294
297
  });
295
- await Promise.all(lambdaPayloads.map(async (payload, index) => {
296
- const callingLambdaTimer = (0, timer_1.timer)('Calling chunk ' + index);
298
+ await Promise.all(lambdaPayloads.map(async (payload) => {
297
299
  await callFunctionWithRetry({ payload, retries: 0, functionName });
298
- callingLambdaTimer.end();
299
300
  }));
300
301
  reqSend.end();
301
302
  let lastProgressUploaded = 0;
@@ -59,7 +59,7 @@ const renderHandler = async (params, options, logs) => {
59
59
  const downloads = {};
60
60
  const inputProps = await inputPropsPromise;
61
61
  await new Promise((resolve, reject) => {
62
- var _a;
62
+ var _a, _b;
63
63
  (0, renderer_1.renderMedia)({
64
64
  composition: {
65
65
  id: params.composition,
@@ -105,14 +105,14 @@ const renderHandler = async (params, options, logs) => {
105
105
  serveUrl: params.serveUrl,
106
106
  quality: params.quality,
107
107
  envVariables: params.envVariables,
108
- dumpBrowserLogs: renderer_1.RenderInternals.isEqualOrBelowLogLevel(params.logLevel, 'verbose'),
108
+ dumpBrowserLogs: (_a = params.dumpBrowserLogs) !== null && _a !== void 0 ? _a : renderer_1.RenderInternals.isEqualOrBelowLogLevel(params.logLevel, 'verbose'),
109
109
  verbose: renderer_1.RenderInternals.isEqualOrBelowLogLevel(params.logLevel, 'verbose'),
110
110
  onBrowserLog: (log) => {
111
111
  logs.push(log);
112
112
  },
113
113
  outputLocation,
114
114
  codec: chunkCodec,
115
- crf: (_a = params.crf) !== null && _a !== void 0 ? _a : undefined,
115
+ crf: (_b = params.crf) !== null && _b !== void 0 ? _b : undefined,
116
116
  pixelFormat: params.pixelFormat,
117
117
  proResProfile: params.proResProfile,
118
118
  onDownload: (src) => {
@@ -77,6 +77,7 @@ const startHandler = async (params, options) => {
77
77
  forceWidth: params.forceWidth,
78
78
  rendererFunctionName: params.rendererFunctionName,
79
79
  audioCodec: params.audioCodec,
80
+ dumpBrowserLogs: params.dumpBrowserLogs,
80
81
  };
81
82
  await (0, aws_clients_1.getLambdaClient)((0, get_current_region_1.getCurrentRegionInFunction)()).send(new client_lambda_1.InvokeCommand({
82
83
  FunctionName: process.env.AWS_LAMBDA_FUNCTION_NAME,
@@ -32,7 +32,7 @@ const io_1 = require("./helpers/io");
32
32
  const validate_composition_1 = require("./helpers/validate-composition");
33
33
  const write_lambda_error_1 = require("./helpers/write-lambda-error");
34
34
  const innerStillHandler = async (lambdaParams, renderId, options) => {
35
- var _a, _b, _c;
35
+ var _a, _b, _c, _d;
36
36
  if (lambdaParams.type !== constants_1.LambdaRoutines.still) {
37
37
  throw new TypeError('Expected still type');
38
38
  }
@@ -119,7 +119,7 @@ const innerStillHandler = async (lambdaParams, renderId, options) => {
119
119
  composition,
120
120
  output: outputPath,
121
121
  serveUrl,
122
- dumpBrowserLogs: false,
122
+ dumpBrowserLogs: (_d = lambdaParams.dumpBrowserLogs) !== null && _d !== void 0 ? _d : renderer_1.RenderInternals.isEqualOrBelowLogLevel(lambdaParams.logLevel, 'verbose'),
123
123
  envVariables: lambdaParams.envVariables,
124
124
  frame: renderer_1.RenderInternals.convertToPositiveFrameIndex({
125
125
  frame: lambdaParams.frame,
package/dist/index.d.ts CHANGED
@@ -44,11 +44,11 @@ import type { LambdaArchitecture } from './shared/validate-architecture';
44
44
  /**
45
45
  * @deprecated Import this from `@remotion/lambda/client` instead
46
46
  */
47
- declare const renderMediaOnLambda: ({ functionName, serveUrl, inputProps, codec, imageFormat, crf, envVariables, pixelFormat, proResProfile, quality, region, maxRetries, composition, framesPerLambda, privacy, logLevel, frameRange, outName, timeoutInMilliseconds, chromiumOptions, scale, numberOfGifLoops, everyNthFrame, concurrencyPerLambda, downloadBehavior, muted, overwrite, audioBitrate, videoBitrate, webhook, forceHeight, forceWidth, rendererFunctionName, forceBucketName: bucketName, audioCodec, }: RenderMediaOnLambdaInput) => Promise<RenderMediaOnLambdaOutput>;
47
+ declare const renderMediaOnLambda: ({ functionName, serveUrl, inputProps, codec, imageFormat, crf, envVariables, pixelFormat, proResProfile, quality, region, maxRetries, composition, framesPerLambda, privacy, logLevel, frameRange, outName, timeoutInMilliseconds, chromiumOptions, scale, numberOfGifLoops, everyNthFrame, concurrencyPerLambda, downloadBehavior, muted, overwrite, audioBitrate, videoBitrate, webhook, forceHeight, forceWidth, rendererFunctionName, forceBucketName: bucketName, audioCodec, dumpBrowserLogs, }: RenderMediaOnLambdaInput) => Promise<RenderMediaOnLambdaOutput>;
48
48
  /**
49
49
  * @deprecated Import this from `@remotion/lambda/client` instead
50
50
  */
51
- declare const renderStillOnLambda: ({ functionName, serveUrl, inputProps, imageFormat, envVariables, quality, region, maxRetries, composition, privacy, frame, logLevel, outName, timeoutInMilliseconds, chromiumOptions, scale, downloadBehavior, forceHeight, forceWidth, forceBucketName, }: RenderStillOnLambdaInput) => Promise<RenderStillOnLambdaOutput>;
51
+ declare const renderStillOnLambda: ({ functionName, serveUrl, inputProps, imageFormat, envVariables, quality, region, maxRetries, composition, privacy, frame, logLevel, outName, timeoutInMilliseconds, chromiumOptions, scale, downloadBehavior, forceHeight, forceWidth, forceBucketName, dumpBrowserLogs, }: RenderStillOnLambdaInput) => Promise<RenderStillOnLambdaOutput>;
52
52
  /**
53
53
  * @deprecated Import this from `@remotion/lambda/client` instead
54
54
  */
@@ -146,6 +146,7 @@ export declare type LambdaPayloads = {
146
146
  forceHeight: number | null;
147
147
  forceWidth: number | null;
148
148
  bucketName: string | null;
149
+ dumpBrowserLogs: boolean;
149
150
  };
150
151
  launch: {
151
152
  rendererFunctionName: string | null;
@@ -183,6 +184,7 @@ export declare type LambdaPayloads = {
183
184
  webhook: WebhookOption;
184
185
  forceHeight: number | null;
185
186
  forceWidth: number | null;
187
+ dumpBrowserLogs: boolean;
186
188
  };
187
189
  status: {
188
190
  type: LambdaRoutines.status;
@@ -226,6 +228,7 @@ export declare type LambdaPayloads = {
226
228
  launchFunctionConfig: {
227
229
  version: string;
228
230
  };
231
+ dumpBrowserLogs: boolean;
229
232
  };
230
233
  still: {
231
234
  type: LambdaRoutines.still;
@@ -249,6 +252,7 @@ export declare type LambdaPayloads = {
249
252
  forceHeight: number | null;
250
253
  forceWidth: number | null;
251
254
  bucketName: string | null;
255
+ dumpBrowserLogs: boolean;
252
256
  };
253
257
  compositions: {
254
258
  type: LambdaRoutines.compositions;
@@ -260,6 +264,7 @@ export declare type LambdaPayloads = {
260
264
  timeoutInMilliseconds: number;
261
265
  serveUrl: string;
262
266
  bucketName: string | null;
267
+ dumpBrowserLogs: boolean;
263
268
  };
264
269
  };
265
270
  export declare type LambdaPayload = LambdaPayloads[LambdaRoutines];
@@ -7,8 +7,9 @@ const validateS3Key = (s3Key) => {
7
7
  if (typeof s3Key !== 'string') {
8
8
  throw new TypeError('The S3 key must be a string. Passed an object of type ' + typeof s3Key);
9
9
  }
10
- if (!s3Key.match(/^([0-9a-zA-Z-!_.*'()/]+)$/g)) {
11
- throw new Error("The S3 Key must match the RegExp `/([0-9a-zA-Z-!_.*'()/]+)/g`. You passed: " +
10
+ // https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html
11
+ if (!s3Key.match(/^([0-9a-zA-Z-!_.*'()/:&$@=;+,?]+)/g)) {
12
+ throw new Error("The S3 Key must match the RegExp `/^([0-9a-zA-Z-!_.*'()/:&$@=;+,?]+)/g`. You passed: " +
12
13
  s3Key +
13
14
  '. Check for invalid characters.');
14
15
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/lambda",
3
- "version": "4.0.0-audio-mixing.5+a9f06aa7d",
3
+ "version": "4.0.0-audio.10+c69c6c306",
4
4
  "description": "Distributed renderer for Remotion based on AWS Lambda",
5
5
  "main": "dist/index.js",
6
6
  "sideEffects": false,
@@ -17,7 +17,7 @@
17
17
  "start": "ts-node src/run.ts",
18
18
  "prepublishOnly": "ts-node src/admin/bundle-lambda.ts && node ensure-version-match.js"
19
19
  },
20
- "author": "",
20
+ "author": "Jonny Burger <jonny@remotion.dev>",
21
21
  "license": "MIT",
22
22
  "repository": {
23
23
  "url": "https://github.com/JonnyBurger/remotion"
@@ -33,18 +33,18 @@
33
33
  "@aws-sdk/credential-providers": "3.272.0",
34
34
  "@aws-sdk/lib-storage": "3.272.0",
35
35
  "@aws-sdk/s3-request-presigner": "3.272.0",
36
- "@remotion/bundler": "4.0.0-audio-mixing.5+a9f06aa7d",
37
- "@remotion/cli": "4.0.0-audio-mixing.5+a9f06aa7d",
38
- "@remotion/renderer": "4.0.0-audio-mixing.5+a9f06aa7d",
36
+ "@remotion/bundler": "4.0.0-audio.10+c69c6c306",
37
+ "@remotion/cli": "4.0.0-audio.10+c69c6c306",
38
+ "@remotion/renderer": "4.0.0-audio.10+c69c6c306",
39
39
  "aws-policies": "^1.0.1",
40
40
  "mime-types": "2.1.34",
41
- "remotion": "4.0.0-audio-mixing.5+a9f06aa7d"
41
+ "remotion": "4.0.0-audio.10+c69c6c306"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@jonny/eslint-config": "3.0.266",
45
- "@remotion/bundler": "3.3.58",
46
- "@remotion/compositor-linux-arm64-musl": "4.0.0-audio-mixing.5+a9f06aa7d",
47
- "@remotion/compositor-linux-x64-musl": "4.0.0-audio-mixing.5+a9f06aa7d",
45
+ "@remotion/bundler": "workspace:*",
46
+ "@remotion/compositor-linux-arm64-musl": "4.0.0-audio.10+c69c6c306",
47
+ "@remotion/compositor-linux-x64-musl": "4.0.0-audio.10+c69c6c306",
48
48
  "@types/mime-types": "2.1.1",
49
49
  "@types/minimist": "1.2.2",
50
50
  "@types/node": "^14.14.14",
@@ -58,12 +58,13 @@
58
58
  "zip-lib": "^0.7.2"
59
59
  },
60
60
  "peerDependencies": {
61
- "@remotion/bundler": "3.3.58"
61
+ "@remotion/bundler": "workspace:*"
62
62
  },
63
63
  "publishConfig": {
64
64
  "access": "public"
65
65
  },
66
66
  "exports": {
67
+ "./package.json": "./package.json",
67
68
  ".": "./dist/index.js",
68
69
  "./defaults": "./dist/defaults.js",
69
70
  "./regions": "./dist/regions.js",
@@ -87,5 +88,5 @@
87
88
  ]
88
89
  }
89
90
  },
90
- "gitHead": "a9f06aa7ddee8bb51f335c68c296557d4d2ed953"
91
+ "gitHead": "c69c6c306051debd05bbddc0ecd6232357ac1f2c"
91
92
  }
Binary file
Binary file