@hyperframes/aws-lambda 0.8.189 → 0.8.191

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.
@@ -10,10 +10,14 @@ function contentStoreUri(bucket, sha256) {
10
10
  }
11
11
  return `s3://${bucket}/${CONTENT_STORE_PREFIX}/${sha256.slice(0, 2)}/${sha256}`;
12
12
  }
13
+ function audioSidecarUri(bucket, sha256) {
14
+ return `${contentStoreUri(bucket, sha256)}.audio.m4a`;
15
+ }
13
16
 
14
17
  export {
15
18
  CONTENT_STORE_PREFIX,
16
19
  CONTENT_STORE_RETENTION_DAYS,
17
20
  CONTENT_STORE_REFRESH_AFTER_DAYS,
18
- contentStoreUri
21
+ contentStoreUri,
22
+ audioSidecarUri
19
23
  };
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  CONTENT_STORE_REFRESH_AFTER_DAYS,
3
+ audioSidecarUri,
3
4
  contentStoreUri
4
- } from "./chunk-YS3KY3C5.js";
5
+ } from "./chunk-3UDA73TT.js";
5
6
 
6
7
  // src/s3Transport.ts
7
8
  import {
@@ -333,6 +334,24 @@ async function putContentStoreFile(client, localPath, bucket, sha256) {
333
334
  if (lastModified) await refreshAgingObject(client, uri, sha256, lastModified, size ?? 0);
334
335
  return outcome;
335
336
  }
337
+ async function putAudioSidecar(client, localPath, bucket, sha256) {
338
+ const uri = audioSidecarUri(bucket, sha256);
339
+ if (await s3ObjectExists(client, uri)) return "reused";
340
+ const { key } = parseS3Uri(uri);
341
+ const size = statSync(localPath).size;
342
+ const own = await sha256File(localPath);
343
+ try {
344
+ if (size > MAX_SINGLE_PUT_BYTES) {
345
+ await uploadFileInParts(client, localPath, size, { Bucket: bucket, Key: key }, own);
346
+ } else {
347
+ await putImmutableBody(client, localPath, bucket, key, size, own, "audio/mp4");
348
+ }
349
+ } catch (error) {
350
+ if (!isS3PreconditionFailed(error)) throw error;
351
+ return "reused";
352
+ }
353
+ return "uploaded";
354
+ }
336
355
  async function ensureContentStoreObject(client, bucket, sha256, sizeBytes) {
337
356
  const uri = contentStoreUri(bucket, sha256);
338
357
  const { key } = parseS3Uri(uri);
@@ -519,8 +538,10 @@ async function downloadSiteFiles(s3, manifestUri, projectDir, leaveVideosRemote)
519
538
  await mapConcurrent(files, 16, async ({ path, sha256, sizeBytes }) => {
520
539
  const uri = contentStoreUri(bucket, sha256);
521
540
  if (leaveVideosRemote && VIDEO_EXTENSIONS_PLANNED_IN_PLACE.has(extname(path).toLowerCase())) {
541
+ const sidecar = audioSidecarUri(bucket, sha256);
522
542
  remoteFiles[path] = {
523
543
  url: await presignS3GetUrl(s3, uri, REMOTE_URL_TTL_SECONDS),
544
+ ...await s3ObjectExists(s3, sidecar) ? { audioSidecarUrl: await presignS3GetUrl(s3, sidecar, REMOTE_URL_TTL_SECONDS) } : {},
524
545
  sha256,
525
546
  sizeBytes
526
547
  };
@@ -551,6 +572,7 @@ export {
551
572
  mapConcurrent,
552
573
  sha256File,
553
574
  putContentStoreFile,
575
+ putAudioSidecar,
554
576
  ensureContentStoreObject,
555
577
  s3ObjectExists,
556
578
  isS3NotFound,
@@ -15,10 +15,10 @@ import {
15
15
  untarDirectory,
16
16
  uploadContentAddressedFileToS3,
17
17
  uploadFileToS3
18
- } from "./chunk-CKQ6SAJX.js";
18
+ } from "./chunk-FQDVV4JA.js";
19
19
  import {
20
20
  CONTENT_STORE_PREFIX
21
- } from "./chunk-YS3KY3C5.js";
21
+ } from "./chunk-3UDA73TT.js";
22
22
 
23
23
  // src/handler.ts
24
24
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync2, readFileSync, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
@@ -345,6 +345,11 @@ function getS3Client() {
345
345
  return cachedS3Client;
346
346
  }
347
347
  async function handler(event, deps) {
348
+ if (event.Action === "warm") {
349
+ primeRuntimeEnv();
350
+ await ensureChromePath(deps);
351
+ return { Action: "warm" };
352
+ }
348
353
  const unwrapped = unwrapEvent(event);
349
354
  validatePlanProtocolShape(unwrapped);
350
355
  validateEventS3Uris(unwrapped);
@@ -9,6 +9,7 @@ import {
9
9
  mapConcurrent,
10
10
  parseS3Uri,
11
11
  parseSiteManifest,
12
+ putAudioSidecar,
12
13
  putContentStoreFile,
13
14
  s3ObjectExists,
14
15
  serializeSiteManifest,
@@ -16,17 +17,23 @@ import {
16
17
  siteManifestUri,
17
18
  siteTarballUri,
18
19
  uploadContentAddressedFileToS3
19
- } from "./chunk-CKQ6SAJX.js";
20
+ } from "./chunk-FQDVV4JA.js";
20
21
  import {
21
- CONTENT_STORE_RETENTION_DAYS
22
- } from "./chunk-YS3KY3C5.js";
22
+ CONTENT_STORE_RETENTION_DAYS,
23
+ audioSidecarUri
24
+ } from "./chunk-3UDA73TT.js";
23
25
 
24
26
  // src/sdk/deploySite.ts
25
27
  import { existsSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
26
28
  import { tmpdir } from "node:os";
27
29
  import { join } from "node:path";
28
30
  import { S3Client } from "@aws-sdk/client-s3";
29
- import { hashProjectDir, listSiteFiles } from "@hyperframes/producer/distributed";
31
+ import {
32
+ buildAudioSidecar,
33
+ hashProjectDir,
34
+ listSiteFiles,
35
+ wantsAudioSidecar
36
+ } from "@hyperframes/producer/distributed";
30
37
  function siteHandleFromId(bucketName, siteId) {
31
38
  const projectS3Uri = siteManifestUri(bucketName, siteId);
32
39
  return { siteId, bucketName, projectS3Uri, bytes: 0, uploadedAt: "", uploaded: false };
@@ -97,6 +104,7 @@ async function deploySite(opts) {
97
104
  uploaded ||= outcome === "uploaded";
98
105
  });
99
106
  stored.sort((a, b) => a.path < b.path ? -1 : 1);
107
+ await storeAudioSidecars(s3, opts, stored);
100
108
  const workdir = mkdtempSync(join(tmpdir(), "hf-deploy-site-"));
101
109
  try {
102
110
  const manifestPath = join(workdir, "files.json");
@@ -122,6 +130,30 @@ async function deploySite(opts) {
122
130
  uploaded
123
131
  };
124
132
  }
133
+ async function storeAudioSidecars(s3, opts, files) {
134
+ const videos = files.filter((file) => wantsAudioSidecar(file.path, file.sizeBytes));
135
+ if (videos.length === 0) return;
136
+ const workdir = mkdtempSync(join(tmpdir(), "hf-audio-sidecar-"));
137
+ let unavailable = false;
138
+ try {
139
+ await mapConcurrent(videos, 2, async ({ path, sha256 }) => {
140
+ if (unavailable || await s3ObjectExists(s3, audioSidecarUri(opts.bucketName, sha256)))
141
+ return;
142
+ const sidecarPath = join(workdir, `${sha256}.m4a`);
143
+ const outcome = await buildAudioSidecar(join(opts.projectDir, path), sidecarPath);
144
+ if (outcome === "unavailable") unavailable = true;
145
+ if (outcome === "built") await putAudioSidecar(s3, sidecarPath, opts.bucketName, sha256);
146
+ rmSync(sidecarPath, { force: true });
147
+ });
148
+ } finally {
149
+ rmSync(workdir, { recursive: true, force: true });
150
+ }
151
+ if (unavailable) {
152
+ opts.onNotice?.(
153
+ "ffmpeg was not found on this machine, so the audio of large videos was not stored on its own: renders will read the whole video for its audio and take longer. Install ffmpeg and run `sites create` again."
154
+ );
155
+ }
156
+ }
125
157
 
126
158
  // src/sdk/validateConfig.ts
127
159
  import { InvalidConfigError } from "@hyperframes/producer/distributed";
@@ -190,6 +222,7 @@ async function renderToLambda(opts) {
190
222
  projectDir: opts.projectDir,
191
223
  bucketName: opts.bucketName,
192
224
  region: opts.region,
225
+ onNotice: opts.onNotice,
193
226
  s3: opts.s3
194
227
  });
195
228
  const input = {
package/dist/cdk/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  CONTENT_STORE_PREFIX,
3
3
  CONTENT_STORE_RETENTION_DAYS
4
- } from "../_chunks/chunk-YS3KY3C5.js";
4
+ } from "../_chunks/chunk-3UDA73TT.js";
5
5
 
6
6
  // src/cdk/HyperframesRenderStack.ts
7
7
  import { dirname, resolve } from "node:path";
@@ -3,3 +3,4 @@ export declare const CONTENT_STORE_RETENTION_DAYS = 30;
3
3
  /** Reusing an object at least this old rewrites it, so a plan's objects outlive it by 23+ days. */
4
4
  export declare const CONTENT_STORE_REFRESH_AFTER_DAYS = 7;
5
5
  export declare function contentStoreUri(bucket: string, sha256: string): string;
6
+ export declare function audioSidecarUri(bucket: string, sha256: string): string;
package/dist/events.d.ts CHANGED
@@ -19,6 +19,9 @@ import type { DistributedFormat, SerializableDistributedRenderConfig } from "@hy
19
19
  export type { SerializableDistributedRenderConfig } from "@hyperframes/producer/distributed";
20
20
  /** Discriminator for the three roles the one Lambda image fulfills. */
21
21
  export type LambdaAction = "plan" | "audio" | "renderChunk" | "assemble";
22
+ export interface WarmEvent {
23
+ Action: "warm";
24
+ }
22
25
  /** Transport protocol selected for one complete distributed render. */
23
26
  export type LambdaPlanProtocol = "v1" | "v2";
24
27
  /**
@@ -29,7 +32,7 @@ export type LambdaPlanProtocol = "v1" | "v2";
29
32
  * handler unwraps both `event.Payload` and `event.Input` before
30
33
  * dispatching.
31
34
  */
32
- export type LambdaEvent = PlanEvent | AudioEvent | RenderChunkEvent | AssembleEvent | {
35
+ export type LambdaEvent = PlanEvent | AudioEvent | RenderChunkEvent | AssembleEvent | WarmEvent | {
33
36
  Payload: LambdaEvent;
34
37
  } | {
35
38
  Input: LambdaEvent;
@@ -199,4 +202,7 @@ export interface AssembleLambdaResult {
199
202
  FileSize: number;
200
203
  DurationMs: number;
201
204
  }
202
- export type LambdaResult = PlanLambdaResult | AudioLambdaResult | RenderChunkLambdaResult | AssembleLambdaResult;
205
+ export interface WarmLambdaResult {
206
+ Action: "warm";
207
+ }
208
+ export type LambdaResult = WarmLambdaResult | PlanLambdaResult | AudioLambdaResult | RenderChunkLambdaResult | AssembleLambdaResult;
package/dist/handler.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  handler,
3
3
  unwrapEvent
4
- } from "./_chunks/chunk-ROTCAPRH.js";
5
- import "./_chunks/chunk-CKQ6SAJX.js";
6
- import "./_chunks/chunk-YS3KY3C5.js";
4
+ } from "./_chunks/chunk-J3OLGMWP.js";
5
+ import "./_chunks/chunk-FQDVV4JA.js";
6
+ import "./_chunks/chunk-3UDA73TT.js";
7
7
  export {
8
8
  handler,
9
9
  unwrapEvent
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  resolveChromeExecutablePath,
7
7
  resolveChromeSource,
8
8
  unwrapEvent
9
- } from "./_chunks/chunk-ROTCAPRH.js";
9
+ } from "./_chunks/chunk-J3OLGMWP.js";
10
10
  import {
11
11
  InvalidConfigError,
12
12
  computeRenderCost,
@@ -14,7 +14,7 @@ import {
14
14
  getRenderProgress,
15
15
  renderToLambda,
16
16
  validateDistributedRenderConfig
17
- } from "./_chunks/chunk-EG2WRNUL.js";
17
+ } from "./_chunks/chunk-L4R3FJOR.js";
18
18
  import {
19
19
  downloadS3ObjectToFile,
20
20
  downloadS3ObjectToFileVerified,
@@ -24,8 +24,8 @@ import {
24
24
  untarDirectory,
25
25
  uploadContentAddressedFileToS3,
26
26
  uploadFileToS3
27
- } from "./_chunks/chunk-CKQ6SAJX.js";
28
- import "./_chunks/chunk-YS3KY3C5.js";
27
+ } from "./_chunks/chunk-FQDVV4JA.js";
28
+ import "./_chunks/chunk-3UDA73TT.js";
29
29
  export {
30
30
  ChromeBinaryUnavailableError,
31
31
  InvalidConfigError,
@@ -65,6 +65,7 @@ export declare function mapConcurrent<T>(values: readonly T[], concurrency: numb
65
65
  export declare function sha256File(path: string): Promise<string>;
66
66
  /** Put a file into the shared content store once; reusing an aging object restarts its expiry. */
67
67
  export declare function putContentStoreFile(client: S3Client, localPath: string, bucket: string, sha256: string): Promise<"uploaded" | "reused">;
68
+ export declare function putAudioSidecar(client: S3Client, localPath: string, bucket: string, sha256: string): Promise<"uploaded" | "reused">;
68
69
  /** Confirm an object the caller never uploaded is in the content store, restarting an aging one. */
69
70
  export declare function ensureContentStoreObject(client: S3Client, bucket: string, sha256: string, sizeBytes: number): Promise<void>;
70
71
  export declare function presignS3GetUrl(client: S3Client, uri: string, expiresInSeconds: number): Promise<string>;
@@ -25,6 +25,8 @@ export interface DeploySiteOptions {
25
25
  * unset, the hash of the project tree picks it.
26
26
  */
27
27
  siteId?: string;
28
+ /** Called with a line a user should see, such as audio sidecars skipped for want of ffmpeg. */
29
+ onNotice?: (message: string) => void;
28
30
  /** Injection seam for tests. Production callers leave unset. */
29
31
  s3?: S3Client;
30
32
  }
package/dist/sdk/index.js CHANGED
@@ -10,11 +10,11 @@ import {
10
10
  validateDistributedRenderConfig,
11
11
  validateStepFunctionsInputSize,
12
12
  validateVariablesPayload
13
- } from "../_chunks/chunk-EG2WRNUL.js";
13
+ } from "../_chunks/chunk-L4R3FJOR.js";
14
14
  import {
15
15
  sha256File
16
- } from "../_chunks/chunk-CKQ6SAJX.js";
17
- import "../_chunks/chunk-YS3KY3C5.js";
16
+ } from "../_chunks/chunk-FQDVV4JA.js";
17
+ import "../_chunks/chunk-3UDA73TT.js";
18
18
 
19
19
  // src/sdk/prebuiltHandler.ts
20
20
  import { existsSync, readFileSync } from "node:fs";
@@ -26,6 +26,7 @@ export interface RenderToLambdaOptions {
26
26
  projectDir?: string;
27
27
  /** Re-use an existing `deploySite` upload (skips tar+S3 PUT). */
28
28
  siteHandle?: SiteHandle;
29
+ onNotice?: (message: string) => void;
29
30
  /** Validated `SerializableDistributedRenderConfig` (no logger / abortSignal). */
30
31
  config: SerializableDistributedRenderConfig;
31
32
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/aws-lambda",
3
- "version": "0.8.189",
3
+ "version": "0.8.191",
4
4
  "description": "AWS Lambda adapter for HyperFrames distributed rendering — handler, client-side SDK, and CDK construct.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -52,7 +52,7 @@
52
52
  "ffprobe-static": "^3.1.0",
53
53
  "puppeteer-core": "^25.8.0",
54
54
  "tar": "^7.4.3",
55
- "@hyperframes/producer": "^0.8.189"
55
+ "@hyperframes/producer": "^0.8.191"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/aws-lambda": "^8.10.146",
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "0.8.189",
3
- "url": "https://github.com/heygen-com/hyperframes/releases/download/v0.8.189/hyperframes-lambda-handler.zip",
4
- "sha256": "95078b7e47160ad95141a051ae8bf772909229c3d626eea59061ddcd5cfb764f",
5
- "bytes": 129250008
2
+ "version": "0.8.191",
3
+ "url": "https://github.com/heygen-com/hyperframes/releases/download/v0.8.191/hyperframes-lambda-handler.zip",
4
+ "sha256": "1f3e446a2c315dd675f283920d6231e3d80fc57a09ba6e16788a1f8ba9470a8d",
5
+ "bytes": 129256219
6
6
  }
@@ -254,6 +254,11 @@ Resources:
254
254
  SelectPlanProtocol:
255
255
  Type: Choice
256
256
  Choices:
257
+ # Only a stack with the arm64 video function has a cold x86 function to warm.
258
+ # Without one the compared value can never match.
259
+ - Variable: $.PlanProtocol
260
+ StringEquals: !If [HasVideoFunction, v2, v2-without-video-function]
261
+ Next: WarmRender
257
262
  - Variable: $.PlanProtocol
258
263
  StringEquals: v2
259
264
  Next: PlanV2
@@ -265,6 +270,23 @@ Resources:
265
270
  Next: UnsupportedPlanProtocol
266
271
  Default: PlanV2
267
272
 
273
+ # The audio task runs on this function once the plan is done; starting its container now hides the cold start.
274
+ WarmRender:
275
+ Type: Task
276
+ Resource: arn:aws:states:::lambda:invoke
277
+ Parameters:
278
+ FunctionName: !GetAtt RenderFunction.Arn
279
+ InvocationType: Event
280
+ Payload:
281
+ Action: warm
282
+ ResultPath: null
283
+ Catch:
284
+ - ErrorEquals:
285
+ - States.ALL
286
+ ResultPath: null
287
+ Next: PlanV2
288
+ Next: PlanV2
289
+
268
290
  UnsupportedPlanProtocol:
269
291
  Type: Fail
270
292
  Error: PLAN_PROTOCOL_UNSUPPORTED
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "0.8.189",
3
- "url": "https://github.com/heygen-com/hyperframes/releases/download/v0.8.189/hyperframes-lambda-video-arm64.oci.tar",
4
- "sha256": "f47d18971cccbdef020abc1e43fb1d5296ece69af7f02e0bde1f951db61aea73",
5
- "bytes": 244629504
2
+ "version": "0.8.191",
3
+ "url": "https://github.com/heygen-com/hyperframes/releases/download/v0.8.191/hyperframes-lambda-video-arm64.oci.tar",
4
+ "sha256": "a2aa52fe187cff37f073a5bccad3778832deb720b036b1df41b4329e197750e1",
5
+ "bytes": 244635136
6
6
  }