@run-cloud/sdk 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -263,6 +263,7 @@ Provider-shaped migration adapters ship in this same package:
263
263
  | Cloudflare Sandbox | `@run-cloud/sdk/compat/cloudflare` |
264
264
  | CodeSandbox | `@run-cloud/sdk/compat/codesandbox` |
265
265
  | Fly Machines | `@run-cloud/sdk/compat/fly` |
266
+ | Remotion Lambda | `@run-cloud/sdk/compat/remotion-lambda` |
266
267
 
267
268
  ```ts
268
269
  // import { Sandbox } from "@vercel/sandbox";
@@ -280,6 +281,117 @@ Provider-specific features that do not have a public run.cloud equivalent throw
280
281
  `UnsupportedCompatibilityFeatureError` with a migration hint. In particular,
281
282
  public port capabilities and filesystem mutation are not silently emulated.
282
283
 
284
+ ## Remotion renders with customer-owned S3
285
+
286
+ run.cloud can replace the Lambda compute used for a Remotion render without
287
+ owning the output bucket. Supply a presigned S3-compatible PUT URL; the sandbox
288
+ renders the video, uploads it directly to that URL, and is destroyed when
289
+ `render()` returns.
290
+
291
+ ```ts
292
+ import { Client } from "@run-cloud/sdk";
293
+
294
+ const cloud = new Client();
295
+ const result = await cloud.remotion.render({
296
+ serveUrl: "https://example.com/remotion-bundle",
297
+ composition: "ProductDemo",
298
+ remotionVersion: "4.0.507",
299
+ inputProps: { customer: "Acme" },
300
+ codec: "h264",
301
+ output: {
302
+ uploadUrl: presignedPutUrl,
303
+ downloadUrl: finalObjectUrl,
304
+ contentType: "video/mp4",
305
+ },
306
+ });
307
+
308
+ console.log(result.outputFile);
309
+ ```
310
+
311
+ `remotionVersion` selects Remotion's matching public, versioned renderer image;
312
+ all Remotion packages in a project should use that exact version. You can pass
313
+ `image` instead to use a custom compatible runtime. Signed URLs are written to
314
+ a private guest file and are not included in command logs.
315
+
316
+ For a smaller migration from `@remotion/lambda/client`, switch the import and
317
+ add `s3Output`; existing `region` and `functionName` fields may remain while
318
+ compute moves to run.cloud:
319
+
320
+ ```ts
321
+ import {
322
+ getRenderProgress,
323
+ renderMediaOnLambda,
324
+ } from "@run-cloud/sdk/compat/remotion-lambda";
325
+
326
+ const render = await renderMediaOnLambda({
327
+ region: "us-east-1",
328
+ functionName: "remotion-render-4-0-507-mem2048mb-disk2048mb-240sec",
329
+ serveUrl: "https://example.com/remotion-bundle",
330
+ composition: "ProductDemo",
331
+ codec: "h264",
332
+ s3Output: {
333
+ bucketName: "customer-video-output",
334
+ uploadUrl: presignedPutUrl,
335
+ downloadUrl: finalObjectUrl,
336
+ },
337
+ });
338
+
339
+ const progress = await getRenderProgress({ renderId: render.renderId });
340
+ ```
341
+
342
+ The adapter derives the exact Remotion image version from the standard Lambda
343
+ function name. By default, `concurrencyPerLambda` controls Remotion workers
344
+ inside one sandbox.
345
+
346
+ To split one video across multiple sandboxes, add a customer-owned temporary
347
+ chunk location. `concurrency` chooses the target maximum sandbox count, while
348
+ `framesPerLambda` can derive it after the SDK selects the composition. Each
349
+ allocated sandbox renders one frame range. For a sufficiently long video,
350
+ `concurrency: 8` creates up to eight sandboxes:
351
+
352
+ ```ts
353
+ const render = await renderMediaOnLambda({
354
+ functionName: "remotion-render-4-0-507-mem2048mb-disk2048mb-240sec",
355
+ serveUrl: "https://example.com/remotion-bundle",
356
+ composition: "ProductDemo",
357
+ codec: "h264",
358
+ concurrency: 8,
359
+ concurrencyPerLambda: 1,
360
+ s3Output: {
361
+ bucketName: "customer-video-output",
362
+ uploadUrl: finalPresignedPutUrl,
363
+ downloadUrl: finalObjectUrl,
364
+ },
365
+ runCloud: {
366
+ distributed: {
367
+ createChunkOutput: async ({ renderId, index }) => {
368
+ const prefix = `remotion-chunks/${renderId}/${index}`;
369
+ return {
370
+ video: await presignTemporaryObject(`${prefix}.ts`),
371
+ audio: await presignTemporaryObject(`${prefix}.aac`),
372
+ };
373
+ },
374
+ },
375
+ },
376
+ });
377
+
378
+ console.log(render.sandboxCount); // exact number actually allocated
379
+ ```
380
+
381
+ `presignTemporaryObject()` returns `{uploadUrl, downloadUrl, headers?,
382
+ contentType?}`. Both URLs point to the same temporary object. Signed URLs stay
383
+ out of shell commands, and the temporary objects are used to produce the final
384
+ artifact in customer S3. Configure an S3 lifecycle rule for the temporary
385
+ prefix.
386
+
387
+ Chunks must be equal-sized except for the last one, so very short or awkwardly
388
+ divisible compositions can use fewer sandboxes than the target. run.cloud still
389
+ does not create or host an S3 bucket, deploy the Remotion site,
390
+ store render metadata, or add a queue/webhook layer. `render.sandboxCount` and
391
+ `progress.runCloud.sandboxCount` report the exact fan-out. `deleteRender()` or
392
+ `cloud.remotion.cancel()` destroys every sandbox associated with the render;
393
+ finite sandbox timeouts remain the cleanup backstop.
394
+
283
395
  `cloud.assets.upload(blob, options)` verifies the uploaded size and MD5 before
284
396
  returning a ready asset. Re-uploading the same bytes reuses the verified object.
285
397
  For multipart archives, `cloud.assets.uploadBatch(parts, { uploadBatchId,
@@ -0,0 +1,82 @@
1
+ import type { RemotionCodec, RemotionDistributedOptions, RemotionRenderOptions, RemotionRenderProgress, RemotionS3Output } from "../remotion.js";
2
+ import { type CompatibilityClientOptions } from "./common.js";
3
+ export interface LambdaCompatibleS3Output extends Omit<RemotionS3Output, "downloadUrl"> {
4
+ /** Returned as bucketName for code that persists the Lambda render handle. */
5
+ bucketName: string;
6
+ downloadUrl: string;
7
+ }
8
+ export interface RenderMediaOnLambdaInput extends CompatibilityClientOptions {
9
+ /** Accepted for source compatibility. Compute runs in run.cloud, not this AWS region. */
10
+ region?: string;
11
+ /** Accepted for source compatibility. No Lambda function is invoked. */
12
+ functionName?: string;
13
+ serveUrl: string;
14
+ composition: string;
15
+ inputProps?: Record<string, unknown>;
16
+ codec: RemotionCodec;
17
+ /** Target maximum run.cloud sandbox count when runCloud.distributed is configured. */
18
+ concurrency?: number;
19
+ /** Frames rendered by each sandbox when runCloud.distributed is configured. */
20
+ framesPerLambda?: number;
21
+ /** Maps to @remotion/renderer concurrency inside the run.cloud sandbox. */
22
+ concurrencyPerLambda?: number | `${number}%`;
23
+ /** The signed S3 output determines object privacy instead. */
24
+ privacy?: "public" | "private" | "no-acl";
25
+ /** Accepted for source compatibility; this thin adapter does not orchestrate retries. */
26
+ maxRetries?: number;
27
+ envVariables?: Record<string, string>;
28
+ crf?: number;
29
+ imageFormat?: "png" | "jpeg" | "none";
30
+ pixelFormat?: string;
31
+ scale?: number;
32
+ muted?: boolean;
33
+ audioCodec?: string;
34
+ metadata?: Record<string, string>;
35
+ /**
36
+ * The only required migration addition: customer-owned S3-compatible signed
37
+ * upload and download locations. run.cloud never owns the bucket.
38
+ */
39
+ s3Output: LambdaCompatibleS3Output;
40
+ runCloud?: Pick<RemotionRenderOptions, "image" | "remotionVersion" | "cpu" | "memory" | "disk" | "timeoutSeconds" | "startupTimeoutSeconds" | "region" | "name" | "idempotencyKey"> & {
41
+ /**
42
+ * Enables multi-sandbox rendering. createChunkOutput presigns temporary
43
+ * video/audio objects in the customer's own S3 bucket.
44
+ */
45
+ distributed?: Omit<RemotionDistributedOptions, "sandboxCount" | "framesPerSandbox"> & {
46
+ sandboxCount?: number;
47
+ framesPerSandbox?: number;
48
+ };
49
+ };
50
+ }
51
+ export interface RenderMediaOnLambdaOutput {
52
+ renderId: string;
53
+ bucketName: string;
54
+ sandboxCount: number;
55
+ }
56
+ export declare function renderMediaOnLambda(input: RenderMediaOnLambdaInput): Promise<RenderMediaOnLambdaOutput>;
57
+ export interface GetRenderProgressInput extends CompatibilityClientOptions {
58
+ renderId: string;
59
+ /** Retained because existing Lambda integrations commonly persist this alongside renderId. */
60
+ bucketName?: string;
61
+ region?: string;
62
+ functionName?: string;
63
+ }
64
+ export interface LambdaCompatibleRenderProgress {
65
+ done: boolean;
66
+ overallProgress: number;
67
+ outputFile: string | null;
68
+ outputSizeInBytes: number | null;
69
+ errors: RemotionRenderProgress["errors"];
70
+ fatalErrorEncountered: boolean;
71
+ currentTime: number;
72
+ renderSize: number;
73
+ runCloud: RemotionRenderProgress;
74
+ }
75
+ export declare function getRenderProgress(input: GetRenderProgressInput): Promise<LambdaCompatibleRenderProgress>;
76
+ export interface DeleteRenderInput extends CompatibilityClientOptions {
77
+ renderId: string;
78
+ bucketName?: string;
79
+ region?: string;
80
+ }
81
+ export declare function deleteRender(input: DeleteRenderInput): Promise<void>;
82
+ export declare function remotionVersionFromFunctionName(functionName: string | undefined): string;
@@ -0,0 +1,68 @@
1
+ import { compatibilityClient, } from "./common.js";
2
+ export async function renderMediaOnLambda(input) {
3
+ if (!input.s3Output?.downloadUrl) {
4
+ throw new TypeError("s3Output.downloadUrl is required");
5
+ }
6
+ const client = compatibilityClient(input);
7
+ const runCloud = input.runCloud ?? {};
8
+ const distributed = runCloud.distributed
9
+ ? {
10
+ ...runCloud.distributed,
11
+ sandboxCount: runCloud.distributed.sandboxCount ?? input.concurrency,
12
+ framesPerSandbox: runCloud.distributed.framesPerSandbox ??
13
+ (runCloud.distributed.sandboxCount === undefined && input.concurrency === undefined
14
+ ? input.framesPerLambda
15
+ : undefined),
16
+ }
17
+ : undefined;
18
+ const handle = await client.remotion.start({
19
+ serveUrl: input.serveUrl,
20
+ composition: input.composition,
21
+ inputProps: input.inputProps,
22
+ codec: input.codec,
23
+ output: input.s3Output,
24
+ concurrency: input.concurrencyPerLambda,
25
+ envVariables: input.envVariables,
26
+ crf: input.crf,
27
+ imageFormat: input.imageFormat,
28
+ pixelFormat: input.pixelFormat,
29
+ scale: input.scale,
30
+ muted: input.muted,
31
+ audioCodec: input.audioCodec,
32
+ metadata: input.metadata,
33
+ ...runCloud,
34
+ distributed,
35
+ remotionVersion: runCloud.remotionVersion ??
36
+ (runCloud.image ? undefined : remotionVersionFromFunctionName(input.functionName)),
37
+ });
38
+ return {
39
+ renderId: handle.renderId,
40
+ bucketName: input.s3Output.bucketName,
41
+ sandboxCount: handle.sandboxCount,
42
+ };
43
+ }
44
+ export async function getRenderProgress(input) {
45
+ const progress = await compatibilityClient(input).remotion.getProgress(input.renderId);
46
+ return {
47
+ done: progress.done,
48
+ overallProgress: progress.progress,
49
+ outputFile: progress.outputFile,
50
+ outputSizeInBytes: progress.outputSizeInBytes,
51
+ errors: progress.errors,
52
+ fatalErrorEncountered: progress.fatalErrorEncountered,
53
+ currentTime: Date.parse(progress.updatedAt),
54
+ renderSize: progress.outputSizeInBytes ?? 0,
55
+ runCloud: progress,
56
+ };
57
+ }
58
+ export async function deleteRender(input) {
59
+ await compatibilityClient(input).remotion.cancel(input.renderId);
60
+ }
61
+ export function remotionVersionFromFunctionName(functionName) {
62
+ const match = functionName?.match(/^remotion-render-(\d+)-(\d+)-(\d+)(?:-|$)/);
63
+ if (!match) {
64
+ throw new TypeError("functionName must use Remotion's naming convention, or runCloud.remotionVersion/image must be supplied");
65
+ }
66
+ return `${match[1]}.${match[2]}.${match[3]}`;
67
+ }
68
+ //# sourceMappingURL=remotion-lambda.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remotion-lambda.js","sourceRoot":"","sources":["../../src/compat/remotion-lambda.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import WebSocket from "ws";
2
+ import { RemotionClient } from "./remotion.js";
3
+ export * from "./remotion.js";
2
4
  export type SimulatorPlatform = "ios" | "android";
3
5
  export interface SimulatorPoint {
4
6
  /** Normalized display-space coordinate, inclusive from 0 (left) to 1 (right). */
@@ -813,6 +815,7 @@ export declare class Client {
813
815
  readonly secrets: SecretsClient;
814
816
  readonly ci: CiClient;
815
817
  readonly images: ImageClient;
818
+ readonly remotion: RemotionClient;
816
819
  private readonly apiKey;
817
820
  private readonly apiUrl;
818
821
  private readonly fetchImpl;
@@ -1268,4 +1271,3 @@ export declare class AssetsClient {
1268
1271
  deleted: boolean;
1269
1272
  }>;
1270
1273
  }
1271
- export {};
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import WebSocket from "ws";
2
2
  import { createHash, randomUUID } from "node:crypto";
3
+ import { RemotionClient } from "./remotion.js";
4
+ export * from "./remotion.js";
3
5
  export const SIMULATOR_KEYS = [
4
6
  "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
5
7
  "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
@@ -699,6 +701,7 @@ export class Client {
699
701
  secrets;
700
702
  ci;
701
703
  images;
704
+ remotion;
702
705
  apiKey;
703
706
  apiUrl;
704
707
  fetchImpl;
@@ -724,6 +727,7 @@ export class Client {
724
727
  this.secrets = new SecretsClient(this);
725
728
  this.ci = new CiClient(this);
726
729
  this.images = new ImageClient(this);
730
+ this.remotion = new RemotionClient(this.sandboxes);
727
731
  }
728
732
  async usage(options = {}) {
729
733
  const query = options.orgId ? `?orgId=${encodeURIComponent(options.orgId)}` : "";