@hyperframes/aws-lambda 0.8.230 → 0.9.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 +7 -8
- package/dist/_chunks/{chunk-QU5TSGIK.js → chunk-2XZPJGCQ.js} +19 -181
- package/dist/_chunks/{chunk-Q6NRBJWH.js → chunk-JZCUNVDN.js} +11 -1
- package/dist/cdk/index.js +4 -123
- package/dist/events.d.ts +19 -68
- package/dist/handler.d.ts +1 -2
- package/dist/handler.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/renderToLambda.d.ts +1 -6
- package/package.json +2 -2
- package/prebuilt/handler-zip.json +4 -4
- package/prebuilt/template.yaml +3 -205
- package/prebuilt/video-image.json +4 -4
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ smoke flow; the SDK + CDK are the supported public surface for adopters.
|
|
|
37
37
|
│ pure functions over local paths
|
|
38
38
|
▼
|
|
39
39
|
┌──────────────────────────────────────────────────────────────────┐
|
|
40
|
-
│ S3 bucket —
|
|
40
|
+
│ S3 bucket — Plan v2 manifest/blobs + chunks + output │
|
|
41
41
|
└──────────────────────────────────────────────────────────────────┘
|
|
42
42
|
```
|
|
43
43
|
|
|
@@ -45,11 +45,11 @@ The handler downloads inputs from S3 into `/tmp`, calls the OSS primitive,
|
|
|
45
45
|
uploads outputs back to S3, and returns a small JSON result that fits
|
|
46
46
|
inside Step Functions' history budget (under 200 bytes per chunk).
|
|
47
47
|
|
|
48
|
-
### Plan transport
|
|
48
|
+
### Plan transport
|
|
49
49
|
|
|
50
|
-
Plan v2 is the
|
|
51
|
-
`renderToLambda` sends
|
|
52
|
-
|
|
50
|
+
Plan v2 is the only supported distributed plan transport.
|
|
51
|
+
`renderToLambda` sends `PlanProtocol: "v2"` without requiring a protocol
|
|
52
|
+
option:
|
|
53
53
|
|
|
54
54
|
```ts
|
|
55
55
|
await renderToLambda({
|
|
@@ -63,8 +63,7 @@ only manifest-selected chunk artifacts, while the assembler fetches its
|
|
|
63
63
|
own metadata and audio subset. Blobs are immutable SHA-256-addressed
|
|
64
64
|
objects, verified on upload and download, and the manifest is published
|
|
65
65
|
last. Unknown protocols and digest mismatches are terminal Step Functions
|
|
66
|
-
errors.
|
|
67
|
-
compatibility by passing `planProtocol: "v1"` explicitly.
|
|
66
|
+
errors.
|
|
68
67
|
|
|
69
68
|
#### Upgrade order
|
|
70
69
|
|
|
@@ -78,7 +77,7 @@ infrastructure upgrade. Before upgrading an application that calls
|
|
|
78
77
|
3. Resume renders, then upgrade the application/SDK dependency.
|
|
79
78
|
|
|
80
79
|
A site is a `sites/<id>/files.json` file list that an older stack cannot
|
|
81
|
-
read
|
|
80
|
+
read. Before it uploads anything,
|
|
82
81
|
`renderToLambda` reads the state machine definition
|
|
83
82
|
(`states:DescribeStateMachine`) and fails with `STACK_TOO_OLD` when the
|
|
84
83
|
stack predates that format. If infrastructure cannot be redeployed first,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
} from "./chunk-4CK3RKM2.js";
|
|
22
22
|
|
|
23
23
|
// src/handler.ts
|
|
24
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync2,
|
|
24
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "node:fs";
|
|
25
25
|
import { availableParallelism, tmpdir as tmpdir2 } from "node:os";
|
|
26
26
|
import { basename, join as join2 } from "node:path";
|
|
27
27
|
import { GetAccountSettingsCommand, InvokeCommand, LambdaClient } from "@aws-sdk/client-lambda";
|
|
@@ -35,7 +35,6 @@ import {
|
|
|
35
35
|
DEFAULT_MAX_PARALLEL_CHUNKS,
|
|
36
36
|
listPlanV2ArtifactsForTarget,
|
|
37
37
|
materializePlanV2Target,
|
|
38
|
-
plan,
|
|
39
38
|
isPlanAudioArtifactPath,
|
|
40
39
|
PLAN_AUDIO_RELATIVE_PATH,
|
|
41
40
|
resolvePlanAudioPath,
|
|
@@ -452,7 +451,7 @@ async function handler(event, deps) {
|
|
|
452
451
|
function normalizeTerminalErrorName(error) {
|
|
453
452
|
if (!error || typeof error !== "object") return;
|
|
454
453
|
const candidate = error;
|
|
455
|
-
if (candidate.code === "PLAN_PROTOCOL_UNSUPPORTED" || candidate.code === "
|
|
454
|
+
if (candidate.code === "PLAN_PROTOCOL_UNSUPPORTED" || candidate.code === "PLAN_V2_INTEGRITY_UNRECOVERABLE" || candidate.code === "FONT_FETCH_FAILED" || candidate.code === "FONT_FETCH_UNAVAILABLE" || candidate.code === "NOT_MEDIA_PAYLOAD" || candidate.code === "VIDEO_SOURCE_UNRENDERABLE" || candidate.code === "VIDEO_EXTRACTION_FAILED" || candidate.code === "INVALID_VIDEO_METADATA" || candidate.code === "FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED" || candidate.code === "CHUNK_EXCEEDS_WORKER_TIMEOUT") {
|
|
456
455
|
candidate.name = candidate.code;
|
|
457
456
|
}
|
|
458
457
|
}
|
|
@@ -486,22 +485,20 @@ function isLambdaAction(value) {
|
|
|
486
485
|
function validatePlanProtocolShape(event) {
|
|
487
486
|
const raw = event;
|
|
488
487
|
const protocol = raw.PlanProtocol;
|
|
489
|
-
if (protocol !== void 0 && protocol !== "
|
|
488
|
+
if (protocol !== void 0 && protocol !== "v2") {
|
|
490
489
|
const error = new Error(
|
|
491
|
-
`[handler] unsupported PlanProtocol ${JSON.stringify(protocol)}; expected "
|
|
490
|
+
`[handler] unsupported PlanProtocol ${JSON.stringify(protocol)}; expected "v2" or absent`
|
|
492
491
|
);
|
|
493
492
|
error.name = "PLAN_PROTOCOL_UNSUPPORTED";
|
|
494
493
|
throw error;
|
|
495
494
|
}
|
|
496
495
|
if (event.Action === "plan" || event.Action === "audio") return;
|
|
497
|
-
const effectiveProtocol = protocol ?? "v2";
|
|
498
496
|
const hasV1Locator = typeof raw.PlanS3Uri === "string";
|
|
499
497
|
const hasV2Manifest = typeof raw.PlanV2ManifestS3Uri === "string";
|
|
500
498
|
const hasV2Prefix = typeof raw.PlanV2ArtifactS3Prefix === "string";
|
|
501
|
-
|
|
502
|
-
if (!valid) {
|
|
499
|
+
if (hasV1Locator || !hasV2Manifest || !hasV2Prefix) {
|
|
503
500
|
const error = new Error(
|
|
504
|
-
`[handler]
|
|
501
|
+
`[handler] v2 ${event.Action} event has mixed or missing plan locators`
|
|
505
502
|
);
|
|
506
503
|
error.name = "PLAN_PROTOCOL_UNSUPPORTED";
|
|
507
504
|
throw error;
|
|
@@ -516,7 +513,7 @@ function summarizeEvent(event) {
|
|
|
516
513
|
return {
|
|
517
514
|
projectS3Uri: event.ProjectS3Uri,
|
|
518
515
|
planOutputS3Prefix: event.PlanOutputS3Prefix,
|
|
519
|
-
planProtocol:
|
|
516
|
+
planProtocol: "v2",
|
|
520
517
|
format: event.Config.format,
|
|
521
518
|
fps: event.Config.fps
|
|
522
519
|
};
|
|
@@ -529,15 +526,15 @@ function summarizeEvent(event) {
|
|
|
529
526
|
};
|
|
530
527
|
case "renderChunk":
|
|
531
528
|
return {
|
|
532
|
-
planProtocol:
|
|
533
|
-
|
|
529
|
+
planProtocol: "v2",
|
|
530
|
+
planV2ManifestS3Uri: event.PlanV2ManifestS3Uri,
|
|
534
531
|
chunkIndex: event.ChunkIndex,
|
|
535
532
|
format: event.Format
|
|
536
533
|
};
|
|
537
534
|
case "assemble":
|
|
538
535
|
return {
|
|
539
|
-
planProtocol:
|
|
540
|
-
|
|
536
|
+
planProtocol: "v2",
|
|
537
|
+
planV2ManifestS3Uri: event.PlanV2ManifestS3Uri,
|
|
541
538
|
chunkCount: event.ChunkS3Uris.length,
|
|
542
539
|
hasAudio: event.AudioS3Uri !== null,
|
|
543
540
|
outputS3Uri: event.OutputS3Uri,
|
|
@@ -596,53 +593,6 @@ async function planConfig(event, deps) {
|
|
|
596
593
|
}
|
|
597
594
|
return config;
|
|
598
595
|
}
|
|
599
|
-
async function handlePlan(event, deps) {
|
|
600
|
-
if (event.PlanProtocol !== "v1") {
|
|
601
|
-
return handlePlanV2(event, deps);
|
|
602
|
-
}
|
|
603
|
-
const started = Date.now();
|
|
604
|
-
const config = await planConfig(event, deps);
|
|
605
|
-
const s3 = deps?.s3 ?? getS3Client();
|
|
606
|
-
const primitive = deps?.primitives?.plan ?? plan;
|
|
607
|
-
await ensureChromePath(deps);
|
|
608
|
-
const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-lambda-plan-"));
|
|
609
|
-
const planDir = join2(work, "plan");
|
|
610
|
-
try {
|
|
611
|
-
const { projectDir } = await fetchProject(s3, event.ProjectS3Uri, work);
|
|
612
|
-
const result = await primitive(projectDir, config, planDir, {
|
|
613
|
-
chunksCarryAudio: true
|
|
614
|
-
});
|
|
615
|
-
const planTar = join2(work, "plan.tar.gz");
|
|
616
|
-
await tarDirectory(planDir, planTar);
|
|
617
|
-
const planTarUri = `${trimTrailingSlash2(event.PlanOutputS3Prefix)}/plan.tar.gz`;
|
|
618
|
-
const audioPath = join2(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
619
|
-
const hasAudio = existsSync2(audioPath) && statSync2(audioPath).size > 0;
|
|
620
|
-
const audioUri = hasAudio ? `${trimTrailingSlash2(event.PlanOutputS3Prefix)}/${PLAN_AUDIO_RELATIVE_PATH}` : null;
|
|
621
|
-
await Promise.all([
|
|
622
|
-
uploadFileToS3(s3, planTar, planTarUri, "application/gzip"),
|
|
623
|
-
hasAudio && audioUri ? uploadFileToS3(s3, audioPath, audioUri, "audio/aac") : null
|
|
624
|
-
]);
|
|
625
|
-
return {
|
|
626
|
-
Action: "plan",
|
|
627
|
-
PlanS3Uri: planTarUri,
|
|
628
|
-
PlanHash: result.planHash,
|
|
629
|
-
ChunkCount: result.chunkCount,
|
|
630
|
-
MaxChunkConcurrency: Math.min(config.workerConcurrency, result.chunkCount),
|
|
631
|
-
TotalFrames: result.totalFrames,
|
|
632
|
-
Fps: result.fps,
|
|
633
|
-
Width: result.width,
|
|
634
|
-
Height: result.height,
|
|
635
|
-
Format: result.format,
|
|
636
|
-
HasAudio: audioUri !== null,
|
|
637
|
-
AudioS3Uri: audioUri,
|
|
638
|
-
FfmpegVersion: result.ffmpegVersion,
|
|
639
|
-
ProducerVersion: result.producerVersion,
|
|
640
|
-
DurationMs: Date.now() - started
|
|
641
|
-
};
|
|
642
|
-
} finally {
|
|
643
|
-
cleanupDir(work);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
596
|
async function fetchProject(s3, projectS3Uri, work, leaveVideosRemote = false) {
|
|
647
597
|
const projectDir = join2(work, "project");
|
|
648
598
|
if (isSiteManifestUri(projectS3Uri)) {
|
|
@@ -658,7 +608,7 @@ async function fetchProject(s3, projectS3Uri, work, leaveVideosRemote = false) {
|
|
|
658
608
|
logEvent({ event: "plan_stage", stage: "untar", ms: Date.now() - stageStarted });
|
|
659
609
|
return { projectDir, remoteFiles: {} };
|
|
660
610
|
}
|
|
661
|
-
async function
|
|
611
|
+
async function handlePlan(event, deps) {
|
|
662
612
|
const started = Date.now();
|
|
663
613
|
const config = await planConfig(event, deps);
|
|
664
614
|
const s3 = deps?.s3 ?? getS3Client();
|
|
@@ -750,58 +700,6 @@ function lambdaMaxCaptureWorkers() {
|
|
|
750
700
|
return Math.max(1, Math.min(byMemory, availableParallelism() - 1));
|
|
751
701
|
}
|
|
752
702
|
async function handleRenderChunk(event, deps) {
|
|
753
|
-
if (event.PlanProtocol !== "v1") {
|
|
754
|
-
return handleRenderChunkV2(event, deps);
|
|
755
|
-
}
|
|
756
|
-
const started = Date.now();
|
|
757
|
-
const s3 = deps?.s3 ?? getS3Client();
|
|
758
|
-
const primitive = deps?.primitives?.renderChunk ?? renderChunk;
|
|
759
|
-
const chromeStarted = Date.now();
|
|
760
|
-
await ensureChromePath(deps);
|
|
761
|
-
const chromeMs = Date.now() - chromeStarted;
|
|
762
|
-
const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-lambda-chunk-"));
|
|
763
|
-
const planTar = join2(work, "plan.tar.gz");
|
|
764
|
-
const planDir = join2(work, "plan");
|
|
765
|
-
try {
|
|
766
|
-
await downloadS3ObjectToFile(s3, event.PlanS3Uri, planTar);
|
|
767
|
-
await untarDirectory(planTar, planDir);
|
|
768
|
-
verifyPlanHash(planDir, event.PlanHash);
|
|
769
|
-
const chunkOutputBase = join2(
|
|
770
|
-
work,
|
|
771
|
-
event.Format === "png-sequence" ? `chunk-${pad(event.ChunkIndex)}` : `chunk-${pad(event.ChunkIndex)}${formatExtension(event.Format)}`
|
|
772
|
-
);
|
|
773
|
-
const primitiveStarted = Date.now();
|
|
774
|
-
const result = await primitive(planDir, event.ChunkIndex, chunkOutputBase, {
|
|
775
|
-
maxCaptureWorkers: lambdaMaxCaptureWorkers(),
|
|
776
|
-
interleaveCapture: true
|
|
777
|
-
});
|
|
778
|
-
logEvent({
|
|
779
|
-
event: "chunk_capture",
|
|
780
|
-
chunkIndex: event.ChunkIndex,
|
|
781
|
-
workers: result.workers,
|
|
782
|
-
captureMode: result.captureMode,
|
|
783
|
-
...chunkStageTimings(result, primitiveStarted - started, chromeMs)
|
|
784
|
-
});
|
|
785
|
-
const chunkUri = await uploadChunkOutput(
|
|
786
|
-
s3,
|
|
787
|
-
result,
|
|
788
|
-
event.ChunkOutputS3Prefix,
|
|
789
|
-
event.ChunkIndex
|
|
790
|
-
);
|
|
791
|
-
return {
|
|
792
|
-
Action: "renderChunk",
|
|
793
|
-
ChunkS3Uri: chunkUri,
|
|
794
|
-
ChunkIndex: event.ChunkIndex,
|
|
795
|
-
Sha256: result.sha256,
|
|
796
|
-
FramesEncoded: result.framesEncoded,
|
|
797
|
-
CaptureMode: result.captureMode,
|
|
798
|
-
DurationMs: Date.now() - started
|
|
799
|
-
};
|
|
800
|
-
} finally {
|
|
801
|
-
cleanupDir(work);
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
async function handleRenderChunkV2(event, deps) {
|
|
805
703
|
const started = Date.now();
|
|
806
704
|
const s3 = deps?.s3 ?? getS3Client();
|
|
807
705
|
const primitive = deps?.primitives?.renderChunk ?? renderChunk;
|
|
@@ -878,47 +776,6 @@ async function uploadChunkOutput(s3, result, prefix, chunkIndex) {
|
|
|
878
776
|
return uri;
|
|
879
777
|
}
|
|
880
778
|
async function handleAssemble(event, deps) {
|
|
881
|
-
if (event.PlanProtocol !== "v1") {
|
|
882
|
-
return handleAssembleV2(event, deps);
|
|
883
|
-
}
|
|
884
|
-
const started = Date.now();
|
|
885
|
-
const s3 = deps?.s3 ?? getS3Client();
|
|
886
|
-
const primitive = deps?.primitives?.assemble ?? assemble;
|
|
887
|
-
const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-lambda-assemble-"));
|
|
888
|
-
const planTar = join2(work, "plan.tar.gz");
|
|
889
|
-
const planDir = join2(work, "plan");
|
|
890
|
-
try {
|
|
891
|
-
await downloadS3ObjectToFile(s3, event.PlanS3Uri, planTar);
|
|
892
|
-
await untarDirectory(planTar, planDir);
|
|
893
|
-
const chunkPaths = await downloadChunkObjects(s3, event.ChunkS3Uris, work, event.Format);
|
|
894
|
-
let audioPath = null;
|
|
895
|
-
if (event.AudioS3Uri) {
|
|
896
|
-
audioPath = resolvePlanAudioPath(planDir) ?? join2(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
897
|
-
await downloadS3ObjectToFile(s3, event.AudioS3Uri, audioPath);
|
|
898
|
-
}
|
|
899
|
-
const finalOutput = event.Format === "png-sequence" ? join2(work, "output-frames") : join2(work, `output${formatExtension(event.Format)}`);
|
|
900
|
-
const result = await primitive(planDir, chunkPaths, audioPath, finalOutput, {
|
|
901
|
-
cfr: event.Cfr === true
|
|
902
|
-
});
|
|
903
|
-
if (event.Format === "png-sequence") {
|
|
904
|
-
const tarball = `${finalOutput}.tar.gz`;
|
|
905
|
-
await tarDirectory(finalOutput, tarball);
|
|
906
|
-
await uploadFileToS3(s3, tarball, event.OutputS3Uri, "application/gzip");
|
|
907
|
-
} else {
|
|
908
|
-
await uploadFileToS3(s3, finalOutput, event.OutputS3Uri);
|
|
909
|
-
}
|
|
910
|
-
return {
|
|
911
|
-
Action: "assemble",
|
|
912
|
-
OutputS3Uri: event.OutputS3Uri,
|
|
913
|
-
FramesEncoded: result.framesEncoded,
|
|
914
|
-
FileSize: result.fileSize,
|
|
915
|
-
DurationMs: Date.now() - started
|
|
916
|
-
};
|
|
917
|
-
} finally {
|
|
918
|
-
cleanupDir(work);
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
async function handleAssembleV2(event, deps) {
|
|
922
779
|
const started = Date.now();
|
|
923
780
|
const s3 = deps?.s3 ?? getS3Client();
|
|
924
781
|
const primitive = deps?.primitives?.assemble ?? assemble;
|
|
@@ -976,10 +833,10 @@ async function handleAssembleV2(event, deps) {
|
|
|
976
833
|
cleanupDir(work);
|
|
977
834
|
}
|
|
978
835
|
}
|
|
979
|
-
async function stitchChunksInS3(s3, event,
|
|
980
|
-
if (event.ChunkS3Uris.length !==
|
|
836
|
+
async function stitchChunksInS3(s3, event, plan, started, lambda) {
|
|
837
|
+
if (event.ChunkS3Uris.length !== plan.chunkCount) {
|
|
981
838
|
throw new Error(
|
|
982
|
-
`[handler] ${event.ChunkS3Uris.length} chunk URIs for a plan of ${
|
|
839
|
+
`[handler] ${event.ChunkS3Uris.length} chunk URIs for a plan of ${plan.chunkCount} chunks`
|
|
983
840
|
);
|
|
984
841
|
}
|
|
985
842
|
const stitchStarted = Date.now();
|
|
@@ -1000,7 +857,7 @@ async function stitchChunksInS3(s3, event, plan2, started, lambda) {
|
|
|
1000
857
|
return {
|
|
1001
858
|
Action: "assemble",
|
|
1002
859
|
OutputS3Uri: event.OutputS3Uri,
|
|
1003
|
-
FramesEncoded:
|
|
860
|
+
FramesEncoded: plan.totalFrames,
|
|
1004
861
|
FileSize: stitched.fileSize,
|
|
1005
862
|
DurationMs: Date.now() - started
|
|
1006
863
|
};
|
|
@@ -1095,10 +952,11 @@ function getEventS3Uris(event) {
|
|
|
1095
952
|
case "audio":
|
|
1096
953
|
return [event.ProjectS3Uri, event.PlanOutputS3Prefix];
|
|
1097
954
|
case "renderChunk":
|
|
1098
|
-
return
|
|
955
|
+
return [event.PlanV2ManifestS3Uri, event.PlanV2ArtifactS3Prefix, event.ChunkOutputS3Prefix];
|
|
1099
956
|
case "assemble":
|
|
1100
957
|
return [
|
|
1101
|
-
|
|
958
|
+
event.PlanV2ManifestS3Uri,
|
|
959
|
+
event.PlanV2ArtifactS3Prefix,
|
|
1102
960
|
...event.ChunkS3Uris,
|
|
1103
961
|
event.OutputS3Uri,
|
|
1104
962
|
event.AudioS3Uri
|
|
@@ -1168,26 +1026,6 @@ function cleanupDir(dir) {
|
|
|
1168
1026
|
} catch {
|
|
1169
1027
|
}
|
|
1170
1028
|
}
|
|
1171
|
-
function verifyPlanHash(planDir, expected) {
|
|
1172
|
-
const planJsonPath = join2(planDir, "plan.json");
|
|
1173
|
-
let parsed;
|
|
1174
|
-
try {
|
|
1175
|
-
parsed = JSON.parse(readFileSync(planJsonPath, "utf-8"));
|
|
1176
|
-
} catch (err) {
|
|
1177
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1178
|
-
const error = new Error(`PLAN_HASH_MISMATCH: failed to read ${planJsonPath}: ${msg}`);
|
|
1179
|
-
error.name = "PLAN_HASH_MISMATCH";
|
|
1180
|
-
throw error;
|
|
1181
|
-
}
|
|
1182
|
-
const actual = parsed.planHash;
|
|
1183
|
-
if (typeof actual !== "string" || actual !== expected) {
|
|
1184
|
-
const error = new Error(
|
|
1185
|
-
`PLAN_HASH_MISMATCH: event PlanHash=${expected} did not match plan.json planHash=${String(actual)}`
|
|
1186
|
-
);
|
|
1187
|
-
error.name = "PLAN_HASH_MISMATCH";
|
|
1188
|
-
throw error;
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
1029
|
|
|
1192
1030
|
export {
|
|
1193
1031
|
ChromeBinaryUnavailableError,
|
|
@@ -221,7 +221,17 @@ function validateStepFunctionsInputSize(input) {
|
|
|
221
221
|
import { randomUUID } from "node:crypto";
|
|
222
222
|
import { DescribeStateMachineCommand, SFNClient, StartExecutionCommand } from "@aws-sdk/client-sfn";
|
|
223
223
|
import { S3Client as S3Client2 } from "@aws-sdk/client-s3";
|
|
224
|
+
function rejectUnsupportedLegacyPlanProtocol(opts) {
|
|
225
|
+
const planProtocol = Reflect.get(opts, "planProtocol");
|
|
226
|
+
if (planProtocol === void 0 || planProtocol === "v2") return;
|
|
227
|
+
const error = new Error(
|
|
228
|
+
`[renderToLambda] planProtocol is no longer configurable; expected "v2" or absent, got ${JSON.stringify(planProtocol)}`
|
|
229
|
+
);
|
|
230
|
+
error.name = "PLAN_PROTOCOL_UNSUPPORTED";
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
224
233
|
async function renderToLambda(opts) {
|
|
234
|
+
rejectUnsupportedLegacyPlanProtocol(opts);
|
|
225
235
|
validateDistributedRenderConfig(opts.config);
|
|
226
236
|
if (!opts.bucketName) {
|
|
227
237
|
throw new Error("[renderToLambda] bucketName is required");
|
|
@@ -258,7 +268,7 @@ async function renderToLambda(opts) {
|
|
|
258
268
|
PlanOutputS3Prefix: planOutputS3Prefix,
|
|
259
269
|
OutputS3Uri: outputS3Uri,
|
|
260
270
|
Config: opts.config,
|
|
261
|
-
PlanProtocol:
|
|
271
|
+
PlanProtocol: "v2"
|
|
262
272
|
};
|
|
263
273
|
validateStepFunctionsInputSize(input);
|
|
264
274
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
package/dist/cdk/index.js
CHANGED
|
@@ -160,8 +160,6 @@ var HyperframesRenderStack = class extends Construct {
|
|
|
160
160
|
"S3_URI_NOT_ALLOWED",
|
|
161
161
|
"BROWSER_GPU_NOT_SOFTWARE",
|
|
162
162
|
"FONT_FETCH_FAILED",
|
|
163
|
-
"PLAN_TOO_LARGE",
|
|
164
|
-
"PlanTooLargeError",
|
|
165
163
|
"PLAN_PROTOCOL_UNSUPPORTED",
|
|
166
164
|
"PlanProtocolUnsupportedError",
|
|
167
165
|
"PLAN_V2_INTEGRITY_UNRECOVERABLE",
|
|
@@ -181,8 +179,6 @@ var HyperframesRenderStack = class extends Construct {
|
|
|
181
179
|
"PLAN_HASH_MISMATCH",
|
|
182
180
|
"S3_URI_NOT_ALLOWED",
|
|
183
181
|
"BROWSER_GPU_NOT_SOFTWARE",
|
|
184
|
-
"PLAN_TOO_LARGE",
|
|
185
|
-
"PlanTooLargeError",
|
|
186
182
|
"PLAN_PROTOCOL_UNSUPPORTED",
|
|
187
183
|
"PlanProtocolUnsupportedError",
|
|
188
184
|
"PLAN_V2_INTEGRITY_UNRECOVERABLE",
|
|
@@ -200,8 +196,6 @@ var HyperframesRenderStack = class extends Construct {
|
|
|
200
196
|
"PLAN_V2_INTEGRITY_UNRECOVERABLE",
|
|
201
197
|
"PlanV2IntegrityError",
|
|
202
198
|
"FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED",
|
|
203
|
-
"PLAN_TOO_LARGE",
|
|
204
|
-
"PlanTooLargeError",
|
|
205
199
|
"PLAN_ARTIFACT_DIGEST_MISMATCH",
|
|
206
200
|
"ChromeBinaryUnavailableError"
|
|
207
201
|
];
|
|
@@ -213,39 +207,6 @@ var HyperframesRenderStack = class extends Construct {
|
|
|
213
207
|
maxDelay: Duration.seconds(60),
|
|
214
208
|
jitterStrategy: sfn.JitterType.FULL
|
|
215
209
|
};
|
|
216
|
-
const plan = new tasks.LambdaInvoke(this, "Plan", {
|
|
217
|
-
lambdaFunction: this.renderFunction,
|
|
218
|
-
payload: sfn.TaskInput.fromObject({
|
|
219
|
-
Action: "plan",
|
|
220
|
-
PlanProtocol: "v1",
|
|
221
|
-
"ProjectS3Uri.$": "$.ProjectS3Uri",
|
|
222
|
-
"PlanOutputS3Prefix.$": "$.PlanOutputS3Prefix",
|
|
223
|
-
"Config.$": "$.Config"
|
|
224
|
-
}),
|
|
225
|
-
resultSelector: {
|
|
226
|
-
PlanProtocol: "v1",
|
|
227
|
-
"PlanS3Uri.$": "$.Payload.PlanS3Uri",
|
|
228
|
-
"PlanHash.$": "$.Payload.PlanHash",
|
|
229
|
-
"ChunkCount.$": "$.Payload.ChunkCount",
|
|
230
|
-
"MaxChunkConcurrency.$": "$.Payload.MaxChunkConcurrency",
|
|
231
|
-
"Format.$": "$.Payload.Format",
|
|
232
|
-
"HasAudio.$": "$.Payload.HasAudio",
|
|
233
|
-
"AudioS3Uri.$": "$.Payload.AudioS3Uri"
|
|
234
|
-
},
|
|
235
|
-
resultPath: "$.Plan"
|
|
236
|
-
});
|
|
237
|
-
plan.addRetry({
|
|
238
|
-
errors: NON_RETRYABLE_PLAN,
|
|
239
|
-
maxAttempts: 0
|
|
240
|
-
});
|
|
241
|
-
plan.addRetry(THROTTLED_RETRY);
|
|
242
|
-
plan.addRetry({
|
|
243
|
-
errors: ["States.ALL"],
|
|
244
|
-
interval: Duration.seconds(2),
|
|
245
|
-
maxAttempts: 4,
|
|
246
|
-
backoffRate: 2,
|
|
247
|
-
maxDelay: Duration.seconds(60)
|
|
248
|
-
});
|
|
249
210
|
const planV2 = new tasks.LambdaInvoke(this, "PlanV2", {
|
|
250
211
|
lambdaFunction: this.renderFunction,
|
|
251
212
|
payload: sfn.TaskInput.fromObject({
|
|
@@ -284,87 +245,9 @@ var HyperframesRenderStack = class extends Construct {
|
|
|
284
245
|
resultPath: "$.Iterator"
|
|
285
246
|
});
|
|
286
247
|
const planProducedZero = new sfn.Fail(this, "PlanProducedZeroChunks", {
|
|
287
|
-
error: "
|
|
248
|
+
error: "PLAN_EMPTY",
|
|
288
249
|
cause: "Plan returned ChunkCount=0 \u2014 non-retryable producer-side invariant violation."
|
|
289
250
|
});
|
|
290
|
-
const renderChunkTask = new tasks.LambdaInvoke(this, "RenderChunk", {
|
|
291
|
-
lambdaFunction: this.renderFunction,
|
|
292
|
-
payload: sfn.TaskInput.fromObject({
|
|
293
|
-
Action: "renderChunk",
|
|
294
|
-
PlanProtocol: "v1",
|
|
295
|
-
"ChunkIndex.$": "$.ChunkIndex",
|
|
296
|
-
"PlanS3Uri.$": "$.PlanS3Uri",
|
|
297
|
-
"PlanHash.$": "$.PlanHash",
|
|
298
|
-
"ChunkOutputS3Prefix.$": "$.ChunkOutputS3Prefix",
|
|
299
|
-
"Format.$": "$.Format"
|
|
300
|
-
}),
|
|
301
|
-
resultSelector: {
|
|
302
|
-
"ChunkS3Uri.$": "$.Payload.ChunkS3Uri",
|
|
303
|
-
"ChunkIndex.$": "$.Payload.ChunkIndex",
|
|
304
|
-
"Sha256.$": "$.Payload.Sha256",
|
|
305
|
-
"FramesEncoded.$": "$.Payload.FramesEncoded",
|
|
306
|
-
"DurationMs.$": "$.Payload.DurationMs"
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
renderChunkTask.addRetry({
|
|
310
|
-
errors: NON_RETRYABLE_CHUNK,
|
|
311
|
-
maxAttempts: 0
|
|
312
|
-
});
|
|
313
|
-
renderChunkTask.addRetry(THROTTLED_RETRY);
|
|
314
|
-
renderChunkTask.addRetry({
|
|
315
|
-
errors: ["States.ALL"],
|
|
316
|
-
interval: Duration.seconds(2),
|
|
317
|
-
maxAttempts: 4,
|
|
318
|
-
backoffRate: 2,
|
|
319
|
-
maxDelay: Duration.seconds(60)
|
|
320
|
-
});
|
|
321
|
-
const renderChunks = new sfn.DistributedMap(this, "RenderChunks", {
|
|
322
|
-
label: "RenderChunks",
|
|
323
|
-
// Standard-type children: express ones stop at 5 minutes and a chunk may run the Lambda's full 15.
|
|
324
|
-
mapExecutionType: sfn.StateMachineType.STANDARD,
|
|
325
|
-
itemsPath: "$.Iterator.ChunkIndexes",
|
|
326
|
-
itemSelector: {
|
|
327
|
-
"ChunkIndex.$": "$$.Map.Item.Value",
|
|
328
|
-
"PlanS3Uri.$": "$.Plan.PlanS3Uri",
|
|
329
|
-
"PlanHash.$": "$.Plan.PlanHash",
|
|
330
|
-
"ChunkOutputS3Prefix.$": "$.PlanOutputS3Prefix",
|
|
331
|
-
"Format.$": "$.Plan.Format"
|
|
332
|
-
},
|
|
333
|
-
// Chunks past the plan's concurrency queue here instead of being throttled by Lambda.
|
|
334
|
-
maxConcurrencyPath: "$.Plan.MaxChunkConcurrency",
|
|
335
|
-
resultPath: "$.Chunks"
|
|
336
|
-
});
|
|
337
|
-
renderChunks.itemProcessor(renderChunkTask);
|
|
338
|
-
const assemble = new tasks.LambdaInvoke(this, "Assemble", {
|
|
339
|
-
lambdaFunction: this.renderFunction,
|
|
340
|
-
payload: sfn.TaskInput.fromObject({
|
|
341
|
-
Action: "assemble",
|
|
342
|
-
PlanProtocol: "v1",
|
|
343
|
-
"PlanS3Uri.$": "$.Plan.PlanS3Uri",
|
|
344
|
-
"ChunkS3Uris.$": "$.Chunks[*].ChunkS3Uri",
|
|
345
|
-
"AudioS3Uri.$": "$.Plan.AudioS3Uri",
|
|
346
|
-
"OutputS3Uri.$": "$.OutputS3Uri",
|
|
347
|
-
"Format.$": "$.Plan.Format"
|
|
348
|
-
}),
|
|
349
|
-
resultSelector: {
|
|
350
|
-
"OutputS3Uri.$": "$.Payload.OutputS3Uri",
|
|
351
|
-
"FramesEncoded.$": "$.Payload.FramesEncoded",
|
|
352
|
-
"FileSize.$": "$.Payload.FileSize"
|
|
353
|
-
},
|
|
354
|
-
resultPath: "$.Output"
|
|
355
|
-
});
|
|
356
|
-
assemble.addRetry({
|
|
357
|
-
errors: NON_RETRYABLE_ASSEMBLE,
|
|
358
|
-
maxAttempts: 0
|
|
359
|
-
});
|
|
360
|
-
assemble.addRetry(THROTTLED_RETRY);
|
|
361
|
-
assemble.addRetry({
|
|
362
|
-
errors: ["States.ALL"],
|
|
363
|
-
interval: Duration.seconds(2),
|
|
364
|
-
maxAttempts: 4,
|
|
365
|
-
backoffRate: 2,
|
|
366
|
-
maxDelay: Duration.seconds(60)
|
|
367
|
-
});
|
|
368
251
|
const renderChunkV2Task = new tasks.LambdaInvoke(this, "RenderChunkV2", {
|
|
369
252
|
lambdaFunction: this.renderFunction,
|
|
370
253
|
payload: sfn.TaskInput.fromObject({
|
|
@@ -472,16 +355,14 @@ var HyperframesRenderStack = class extends Construct {
|
|
|
472
355
|
resultSelector: { "Chunks.$": "$[0].Chunks", "Audio.$": "$[1].Audio" },
|
|
473
356
|
resultPath: "$.Render"
|
|
474
357
|
}).branch(renderChunksV2).branch(audioNeeded);
|
|
475
|
-
const
|
|
476
|
-
const assertChunkCount = new sfn.Choice(this, "AssertChunkCount").when(sfn.Condition.numberGreaterThan("$.Plan.ChunkCount", 0), selectWorkerProtocol).otherwise(planProducedZero);
|
|
477
|
-
plan.next(buildChunkList);
|
|
358
|
+
const assertChunkCount = new sfn.Choice(this, "AssertChunkCount").when(sfn.Condition.numberGreaterThan("$.Plan.ChunkCount", 0), renderV2.next(assembleV2)).otherwise(planProducedZero);
|
|
478
359
|
planV2.next(buildChunkList);
|
|
479
360
|
buildChunkList.next(assertChunkCount);
|
|
480
361
|
const unsupportedPlanProtocol = new sfn.Fail(this, "UnsupportedPlanProtocol", {
|
|
481
362
|
error: "PLAN_PROTOCOL_UNSUPPORTED",
|
|
482
|
-
cause: 'PlanProtocol must be "
|
|
363
|
+
cause: 'PlanProtocol must be "v2" or absent (defaults to v2).'
|
|
483
364
|
});
|
|
484
|
-
return new sfn.Choice(this, "SelectPlanProtocol").when(sfn.Condition.stringEquals("$.PlanProtocol", "v2"), planV2).when(sfn.Condition.
|
|
365
|
+
return new sfn.Choice(this, "SelectPlanProtocol").when(sfn.Condition.stringEquals("$.PlanProtocol", "v2"), planV2).when(sfn.Condition.isPresent("$.PlanProtocol"), unsupportedPlanProtocol).otherwise(planV2);
|
|
485
366
|
}
|
|
486
367
|
};
|
|
487
368
|
function defaultHandlerZipPath() {
|
package/dist/events.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* dispatches on the `Action` field. Each action maps 1:1 onto one of the
|
|
6
6
|
* three OSS distributed primitives:
|
|
7
7
|
*
|
|
8
|
-
* "plan" → `
|
|
8
|
+
* "plan" → `planV2WithPublisher(...)` (Activity A)
|
|
9
9
|
* "renderChunk" → `renderChunk(planDir, chunkIndex, output)` (Activity B)
|
|
10
10
|
* "assemble" → `assemble(planDir, chunkPaths, audio, out)` (Activity C)
|
|
11
11
|
*
|
|
@@ -37,8 +37,6 @@ export interface StitchPartsEvent {
|
|
|
37
37
|
}[];
|
|
38
38
|
}[];
|
|
39
39
|
}
|
|
40
|
-
/** Transport protocol selected for one complete distributed render. */
|
|
41
|
-
export type LambdaPlanProtocol = "v1" | "v2";
|
|
42
40
|
/**
|
|
43
41
|
* Top-level shape of any event the handler may receive.
|
|
44
42
|
*
|
|
@@ -53,7 +51,7 @@ export type LambdaEvent = PlanEvent | AudioEvent | RenderChunkEvent | AssembleEv
|
|
|
53
51
|
Input: LambdaEvent;
|
|
54
52
|
};
|
|
55
53
|
/** Activity A: produce a planDir, upload to S3. */
|
|
56
|
-
interface
|
|
54
|
+
export interface PlanEvent {
|
|
57
55
|
Action: "plan";
|
|
58
56
|
/** S3 URI pointing at a `tar -czf`-archived project directory (`s3://bucket/key.tar.gz`). */
|
|
59
57
|
ProjectS3Uri: string;
|
|
@@ -61,33 +59,23 @@ interface PlanEventBase {
|
|
|
61
59
|
PlanOutputS3Prefix: string;
|
|
62
60
|
/** `DistributedRenderConfig` minus runtime-only fields (logger, abortSignal). */
|
|
63
61
|
Config: SerializableDistributedRenderConfig;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Legacy plan transport. Callers must select it explicitly.
|
|
67
|
-
*
|
|
68
|
-
* @deprecated Use {@link PlanV2Event} for new integrations.
|
|
69
|
-
*/
|
|
70
|
-
export interface PlanV1Event extends PlanEventBase {
|
|
71
|
-
PlanProtocol: "v1";
|
|
72
|
-
}
|
|
73
|
-
/** Default content-addressed v2 plan transport. */
|
|
74
|
-
export interface PlanV2Event extends PlanEventBase {
|
|
62
|
+
/** Optional for direct handler callers; the SDK and orchestrator send it explicitly. */
|
|
75
63
|
PlanProtocol?: "v2";
|
|
76
64
|
}
|
|
77
|
-
|
|
65
|
+
/** Compatibility alias for the only supported plan event. */
|
|
66
|
+
export type PlanV2Event = PlanEvent;
|
|
78
67
|
/** Makes a v2 plan's audio beside its chunks, from the same project and config as the plan. */
|
|
79
|
-
export interface AudioEvent extends Omit<
|
|
68
|
+
export interface AudioEvent extends Omit<PlanEvent, "Action" | "PlanProtocol"> {
|
|
80
69
|
Action: "audio";
|
|
81
70
|
}
|
|
82
71
|
/** Activity B: fetch planDir, render one chunk, upload result. */
|
|
83
|
-
interface
|
|
72
|
+
export interface RenderChunkEvent {
|
|
84
73
|
Action: "renderChunk";
|
|
85
74
|
/**
|
|
86
|
-
* `PlanResult.planHash` from the Plan invocation.
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* machine routes it as non-retryable.
|
|
75
|
+
* `PlanResult.planHash` from the Plan invocation. The handler verifies it
|
|
76
|
+
* against the content-addressed manifest before invoking the producer.
|
|
77
|
+
* Divergence throws a typed `PLAN_HASH_MISMATCH` so the state machine routes
|
|
78
|
+
* it as non-retryable.
|
|
91
79
|
*/
|
|
92
80
|
PlanHash: string;
|
|
93
81
|
/** 0-based chunk index this invocation should render. */
|
|
@@ -96,29 +84,14 @@ interface RenderChunkEventBase {
|
|
|
96
84
|
ChunkOutputS3Prefix: string;
|
|
97
85
|
/** Output container format from the plan's encoder.json; drives file vs frame-dir handling. */
|
|
98
86
|
Format: DistributedFormat;
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Legacy chunk event. Callers must select it explicitly.
|
|
102
|
-
*
|
|
103
|
-
* @deprecated Use {@link RenderChunkV2Event} for new integrations.
|
|
104
|
-
*/
|
|
105
|
-
export interface RenderChunkV1Event extends RenderChunkEventBase {
|
|
106
|
-
PlanProtocol: "v1";
|
|
107
|
-
/** S3 URI of the v1 plan tar produced by a PlanEvent invocation. */
|
|
108
|
-
PlanS3Uri: string;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* V2 chunk event. It intentionally cannot carry `PlanS3Uri`: the manifest
|
|
112
|
-
* describes the exact content-addressed artifacts needed by this chunk.
|
|
113
|
-
*/
|
|
114
|
-
export interface RenderChunkV2Event extends RenderChunkEventBase {
|
|
115
87
|
PlanProtocol?: "v2";
|
|
116
88
|
PlanV2ManifestS3Uri: string;
|
|
117
89
|
PlanV2ArtifactS3Prefix: string;
|
|
118
90
|
}
|
|
119
|
-
|
|
91
|
+
/** Compatibility alias for the only supported chunk event. */
|
|
92
|
+
export type RenderChunkV2Event = RenderChunkEvent;
|
|
120
93
|
/** Activity C: fetch planDir + all chunks + audio, assemble, upload final. */
|
|
121
|
-
interface
|
|
94
|
+
export interface AssembleEvent {
|
|
122
95
|
Action: "assemble";
|
|
123
96
|
/** S3 URIs of every chunk, ordered by chunk index. Length must equal `chunkCount`. */
|
|
124
97
|
ChunkS3Uris: string[];
|
|
@@ -138,27 +111,15 @@ interface AssembleEventBase {
|
|
|
138
111
|
* unset preserves current `-c copy` behavior.
|
|
139
112
|
*/
|
|
140
113
|
Cfr?: boolean;
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Legacy assemble event. Callers must select it explicitly.
|
|
144
|
-
*
|
|
145
|
-
* @deprecated Use {@link AssembleV2Event} for new integrations.
|
|
146
|
-
*/
|
|
147
|
-
export interface AssembleV1Event extends AssembleEventBase {
|
|
148
|
-
PlanProtocol: "v1";
|
|
149
|
-
/** S3 URI of the v1 plan tar produced by a PlanEvent invocation. */
|
|
150
|
-
PlanS3Uri: string;
|
|
151
|
-
}
|
|
152
|
-
/** V2 assemble event, scoped to manifest-declared assembler artifacts. */
|
|
153
|
-
export interface AssembleV2Event extends AssembleEventBase {
|
|
154
114
|
PlanProtocol?: "v2";
|
|
155
115
|
PlanV2ManifestS3Uri: string;
|
|
156
116
|
PlanV2ArtifactS3Prefix: string;
|
|
157
117
|
PlanHash: string;
|
|
158
118
|
}
|
|
159
|
-
|
|
119
|
+
/** Compatibility alias for the only supported assemble event. */
|
|
120
|
+
export type AssembleV2Event = AssembleEvent;
|
|
160
121
|
/** Result of a `plan` invocation. Carries enough to size the Map(N) state. */
|
|
161
|
-
interface
|
|
122
|
+
export interface PlanLambdaResult {
|
|
162
123
|
Action: "plan";
|
|
163
124
|
PlanHash: string;
|
|
164
125
|
ChunkCount: number;
|
|
@@ -173,24 +134,14 @@ interface PlanLambdaResultBase {
|
|
|
173
134
|
FfmpegVersion: string;
|
|
174
135
|
ProducerVersion: string;
|
|
175
136
|
DurationMs: number;
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Existing v1 result. Kept unchanged for wire compatibility.
|
|
179
|
-
*
|
|
180
|
-
* @deprecated New integrations should consume {@link PlanV2LambdaResult}.
|
|
181
|
-
*/
|
|
182
|
-
export interface PlanV1LambdaResult extends PlanLambdaResultBase {
|
|
183
|
-
PlanS3Uri: string;
|
|
184
|
-
}
|
|
185
|
-
/** V2 result. The two v2 locators are never aliases for `PlanS3Uri`. */
|
|
186
|
-
export interface PlanV2LambdaResult extends PlanLambdaResultBase {
|
|
187
137
|
PlanProtocol: "v2";
|
|
188
138
|
PlanV2ManifestS3Uri: string;
|
|
189
139
|
PlanV2ArtifactS3Prefix: string;
|
|
190
140
|
/** The plan left its audio to the audio task, which has a worker kept free for it. */
|
|
191
141
|
SeparateAudio: boolean;
|
|
192
142
|
}
|
|
193
|
-
|
|
143
|
+
/** Compatibility alias for the only supported plan result. */
|
|
144
|
+
export type PlanV2LambdaResult = PlanLambdaResult;
|
|
194
145
|
export interface AudioLambdaResult {
|
|
195
146
|
Action: "audio";
|
|
196
147
|
HasAudio: boolean;
|
package/dist/handler.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { LambdaClient } from "@aws-sdk/client-lambda";
|
|
14
14
|
import { S3Client } from "@aws-sdk/client-s3";
|
|
15
|
-
import { assemble, buildPlanAudio, type ChunkRenderer,
|
|
15
|
+
import { assemble, buildPlanAudio, type ChunkRenderer, planV2WithPublisher } from "@hyperframes/producer/distributed";
|
|
16
16
|
import { type StitchHelper } from "./s3Stitch.js";
|
|
17
17
|
import type { AssembleEvent, AudioEvent, LambdaEvent, LambdaResult, PlanEvent, RenderChunkEvent } from "./events.js";
|
|
18
18
|
/**
|
|
@@ -27,7 +27,6 @@ export interface HandlerDeps {
|
|
|
27
27
|
/** Reads the account's concurrency when the render does not set one. */
|
|
28
28
|
lambda?: LambdaClient;
|
|
29
29
|
primitives?: {
|
|
30
|
-
plan: typeof plan;
|
|
31
30
|
planV2WithPublisher?: typeof planV2WithPublisher;
|
|
32
31
|
buildPlanAudio?: typeof buildPlanAudio;
|
|
33
32
|
renderChunk: ChunkRenderer;
|
package/dist/handler.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* install it separately.
|
|
23
23
|
*/
|
|
24
24
|
export { handler, type HandlerDeps, unwrapEvent } from "./handler.js";
|
|
25
|
-
export { type AssembleEvent, type AssembleLambdaResult, type LambdaAction, type LambdaEvent, type
|
|
25
|
+
export { type AssembleEvent, type AssembleLambdaResult, type LambdaAction, type LambdaEvent, type LambdaResult, type PlanEvent, type PlanLambdaResult, type PlanV2Event, type PlanV2LambdaResult, type RenderChunkEvent, type RenderChunkLambdaResult, type RenderChunkV2Event, type AssembleV2Event, type SerializableDistributedRenderConfig, } from "./events.js";
|
|
26
26
|
export { ChromeBinaryUnavailableError, type ChromeSource, resolveChromeArgs, resolveChromeExecutablePath, resolveChromeSource, } from "./chromium.js";
|
|
27
27
|
export { downloadS3ObjectToFile, downloadS3ObjectToFileVerified, formatS3Uri, parseS3Uri, type S3Location, tarDirectory, untarDirectory, uploadContentAddressedFileToS3, uploadFileToS3, } from "./s3Transport.js";
|
|
28
28
|
export { S3PlanV2ArtifactPublisher, type S3PlanV2ArtifactPublisherOptions, } from "./s3PlanV2Publisher.js";
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
resolveChromeExecutablePath,
|
|
7
7
|
resolveChromeSource,
|
|
8
8
|
unwrapEvent
|
|
9
|
-
} from "./_chunks/chunk-
|
|
9
|
+
} from "./_chunks/chunk-2XZPJGCQ.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-
|
|
17
|
+
} from "./_chunks/chunk-JZCUNVDN.js";
|
|
18
18
|
import {
|
|
19
19
|
downloadS3ObjectToFile,
|
|
20
20
|
downloadS3ObjectToFileVerified,
|
package/dist/sdk/index.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { SFNClient } from "@aws-sdk/client-sfn";
|
|
20
20
|
import { S3Client } from "@aws-sdk/client-s3";
|
|
21
|
-
import type {
|
|
21
|
+
import type { SerializableDistributedRenderConfig } from "../events.js";
|
|
22
22
|
import { type SiteHandle } from "./deploySite.js";
|
|
23
23
|
/** Options for {@link renderToLambda}. */
|
|
24
24
|
export interface RenderToLambdaOptions {
|
|
@@ -29,11 +29,6 @@ export interface RenderToLambdaOptions {
|
|
|
29
29
|
onNotice?: (message: string) => void;
|
|
30
30
|
/** Validated `SerializableDistributedRenderConfig` (no logger / abortSignal). */
|
|
31
31
|
config: SerializableDistributedRenderConfig;
|
|
32
|
-
/**
|
|
33
|
-
* Distributed plan transport. Defaults to `"v2"`. Select `"v1"`
|
|
34
|
-
* explicitly only for deprecated compatibility with the monolithic plan.
|
|
35
|
-
*/
|
|
36
|
-
planProtocol?: LambdaPlanProtocol;
|
|
37
32
|
/** S3 bucket from the SAM stack output (`RenderBucketName`). */
|
|
38
33
|
bucketName: string;
|
|
39
34
|
/** State machine ARN from the SAM stack output (`RenderStateMachineArn`). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/aws-lambda",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
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.
|
|
55
|
+
"@hyperframes/producer": "^0.9.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/aws-lambda": "^8.10.146",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
3
|
-
"url": "https://github.com/heygen-com/hyperframes/releases/download/v0.
|
|
4
|
-
"sha256": "
|
|
5
|
-
"bytes":
|
|
2
|
+
"version": "0.9.0",
|
|
3
|
+
"url": "https://github.com/heygen-com/hyperframes/releases/download/v0.9.0/hyperframes-lambda-handler.zip",
|
|
4
|
+
"sha256": "d32f485d4ca6c0e707f36e3457df24ea29487035167c13aff9ccbeb328bc372d",
|
|
5
|
+
"bytes": 129520617
|
|
6
6
|
}
|
package/prebuilt/template.yaml
CHANGED
|
@@ -274,9 +274,6 @@ Resources:
|
|
|
274
274
|
- Variable: $.PlanProtocol
|
|
275
275
|
StringEquals: v2
|
|
276
276
|
Next: PlanV2
|
|
277
|
-
- Variable: $.PlanProtocol
|
|
278
|
-
StringEquals: v1
|
|
279
|
-
Next: Plan
|
|
280
277
|
- Variable: $.PlanProtocol
|
|
281
278
|
IsPresent: true
|
|
282
279
|
Next: UnsupportedPlanProtocol
|
|
@@ -302,72 +299,7 @@ Resources:
|
|
|
302
299
|
UnsupportedPlanProtocol:
|
|
303
300
|
Type: Fail
|
|
304
301
|
Error: PLAN_PROTOCOL_UNSUPPORTED
|
|
305
|
-
Cause: PlanProtocol must be "
|
|
306
|
-
|
|
307
|
-
Plan:
|
|
308
|
-
Type: Task
|
|
309
|
-
Resource: arn:aws:states:::lambda:invoke
|
|
310
|
-
Parameters:
|
|
311
|
-
# Chunks must run the ffmpeg build the plan records, so the plan runs where they do.
|
|
312
|
-
FunctionName:
|
|
313
|
-
!If [
|
|
314
|
-
HasVideoFunction,
|
|
315
|
-
!GetAtt VideoFunctionStack.Outputs.FunctionArn,
|
|
316
|
-
!GetAtt RenderFunction.Arn,
|
|
317
|
-
]
|
|
318
|
-
Payload:
|
|
319
|
-
Action: plan
|
|
320
|
-
PlanProtocol: v1
|
|
321
|
-
ProjectS3Uri.$: "$.ProjectS3Uri"
|
|
322
|
-
PlanOutputS3Prefix.$: "$.PlanOutputS3Prefix"
|
|
323
|
-
Config.$: "$.Config"
|
|
324
|
-
ResultSelector:
|
|
325
|
-
PlanProtocol: v1
|
|
326
|
-
PlanS3Uri.$: "$.Payload.PlanS3Uri"
|
|
327
|
-
PlanHash.$: "$.Payload.PlanHash"
|
|
328
|
-
ChunkCount.$: "$.Payload.ChunkCount"
|
|
329
|
-
MaxChunkConcurrency.$: "$.Payload.MaxChunkConcurrency"
|
|
330
|
-
Format.$: "$.Payload.Format"
|
|
331
|
-
HasAudio.$: "$.Payload.HasAudio"
|
|
332
|
-
AudioS3Uri.$: "$.Payload.AudioS3Uri"
|
|
333
|
-
ResultPath: $.Plan
|
|
334
|
-
Retry:
|
|
335
|
-
- ErrorEquals:
|
|
336
|
-
# These error names are thrown by the producer's plan
|
|
337
|
-
# stage when retrying can never help — version skew,
|
|
338
|
-
# determinism violations, GPU misconfiguration, font
|
|
339
|
-
# fetch failures, plan-size cap, unsupported format.
|
|
340
|
-
# Fail fast rather than burning ~120s of retry budget.
|
|
341
|
-
- FFMPEG_VERSION_MISMATCH
|
|
342
|
-
- PLAN_HASH_MISMATCH
|
|
343
|
-
- BROWSER_GPU_NOT_SOFTWARE
|
|
344
|
-
- FONT_FETCH_FAILED
|
|
345
|
-
- PLAN_TOO_LARGE
|
|
346
|
-
- PlanTooLargeError
|
|
347
|
-
- PLAN_PROTOCOL_UNSUPPORTED
|
|
348
|
-
- PlanProtocolUnsupportedError
|
|
349
|
-
- VIDEO_SOURCE_UNRENDERABLE
|
|
350
|
-
- INVALID_VIDEO_METADATA
|
|
351
|
-
- NOT_MEDIA_PAYLOAD
|
|
352
|
-
- NotMediaPayloadError
|
|
353
|
-
- PLAN_ARTIFACT_DIGEST_MISMATCH
|
|
354
|
-
- FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED
|
|
355
|
-
- CHUNK_EXCEEDS_WORKER_TIMEOUT
|
|
356
|
-
- SITE_MANIFEST_INVALID
|
|
357
|
-
MaxAttempts: 0
|
|
358
|
-
# Another render on the same function can still take the reserved slots; wait minutes.
|
|
359
|
-
- ErrorEquals: [Lambda.TooManyRequestsException]
|
|
360
|
-
IntervalSeconds: 5
|
|
361
|
-
MaxAttempts: 10
|
|
362
|
-
BackoffRate: 2
|
|
363
|
-
MaxDelaySeconds: 60
|
|
364
|
-
JitterStrategy: FULL
|
|
365
|
-
- ErrorEquals: [States.ALL]
|
|
366
|
-
IntervalSeconds: 2
|
|
367
|
-
MaxAttempts: 4
|
|
368
|
-
BackoffRate: 2
|
|
369
|
-
MaxDelaySeconds: 60
|
|
370
|
-
Next: BuildChunkList
|
|
302
|
+
Cause: PlanProtocol must be "v2" or absent (defaults to v2).
|
|
371
303
|
|
|
372
304
|
PlanV2:
|
|
373
305
|
Type: Task
|
|
@@ -403,8 +335,6 @@ Resources:
|
|
|
403
335
|
- S3_URI_NOT_ALLOWED
|
|
404
336
|
- BROWSER_GPU_NOT_SOFTWARE
|
|
405
337
|
- FONT_FETCH_FAILED
|
|
406
|
-
- PLAN_TOO_LARGE
|
|
407
|
-
- PlanTooLargeError
|
|
408
338
|
- PLAN_PROTOCOL_UNSUPPORTED
|
|
409
339
|
- PlanProtocolUnsupportedError
|
|
410
340
|
- PLAN_V2_INTEGRITY_UNRECOVERABLE
|
|
@@ -453,140 +383,14 @@ Resources:
|
|
|
453
383
|
Choices:
|
|
454
384
|
- Variable: $.Plan.ChunkCount
|
|
455
385
|
NumericGreaterThan: 0
|
|
456
|
-
Next:
|
|
386
|
+
Next: RenderV2
|
|
457
387
|
Default: PlanProducedZeroChunks
|
|
458
388
|
|
|
459
389
|
PlanProducedZeroChunks:
|
|
460
390
|
Type: Fail
|
|
461
|
-
Error:
|
|
391
|
+
Error: PLAN_EMPTY
|
|
462
392
|
Cause: Plan returned ChunkCount=0 — non-retryable producer-side invariant violation.
|
|
463
393
|
|
|
464
|
-
SelectWorkerProtocol:
|
|
465
|
-
Type: Choice
|
|
466
|
-
Choices:
|
|
467
|
-
- Variable: $.Plan.PlanProtocol
|
|
468
|
-
StringEquals: v2
|
|
469
|
-
Next: RenderV2
|
|
470
|
-
Default: RenderChunks
|
|
471
|
-
|
|
472
|
-
RenderChunks:
|
|
473
|
-
Type: Map
|
|
474
|
-
Label: RenderChunks
|
|
475
|
-
ItemsPath: $.Iterator.ChunkIndexes
|
|
476
|
-
ItemSelector:
|
|
477
|
-
ChunkIndex.$: "$$.Map.Item.Value"
|
|
478
|
-
PlanS3Uri.$: "$.Plan.PlanS3Uri"
|
|
479
|
-
PlanHash.$: "$.Plan.PlanHash"
|
|
480
|
-
ChunkOutputS3Prefix.$: "$.PlanOutputS3Prefix"
|
|
481
|
-
Format.$: "$.Plan.Format"
|
|
482
|
-
# Chunks past the plan's concurrency queue here instead of being throttled by Lambda.
|
|
483
|
-
MaxConcurrencyPath: $.Plan.MaxChunkConcurrency
|
|
484
|
-
ResultPath: $.Chunks
|
|
485
|
-
ItemProcessor:
|
|
486
|
-
ProcessorConfig:
|
|
487
|
-
# Inline Map runs at most 40 iterations at once. STANDARD children
|
|
488
|
-
# because EXPRESS ones stop at 5 minutes and a chunk may run 15.
|
|
489
|
-
Mode: DISTRIBUTED
|
|
490
|
-
ExecutionType: STANDARD
|
|
491
|
-
StartAt: RenderChunk
|
|
492
|
-
States:
|
|
493
|
-
RenderChunk:
|
|
494
|
-
Type: Task
|
|
495
|
-
Resource: arn:aws:states:::lambda:invoke
|
|
496
|
-
Parameters:
|
|
497
|
-
FunctionName:
|
|
498
|
-
!If [
|
|
499
|
-
HasVideoFunction,
|
|
500
|
-
!GetAtt VideoFunctionStack.Outputs.FunctionArn,
|
|
501
|
-
!GetAtt RenderFunction.Arn,
|
|
502
|
-
]
|
|
503
|
-
Payload:
|
|
504
|
-
Action: renderChunk
|
|
505
|
-
PlanProtocol: v1
|
|
506
|
-
ChunkIndex.$: "$.ChunkIndex"
|
|
507
|
-
PlanS3Uri.$: "$.PlanS3Uri"
|
|
508
|
-
PlanHash.$: "$.PlanHash"
|
|
509
|
-
ChunkOutputS3Prefix.$: "$.ChunkOutputS3Prefix"
|
|
510
|
-
Format.$: "$.Format"
|
|
511
|
-
ResultSelector:
|
|
512
|
-
ChunkS3Uri.$: "$.Payload.ChunkS3Uri"
|
|
513
|
-
ChunkIndex.$: "$.Payload.ChunkIndex"
|
|
514
|
-
Sha256.$: "$.Payload.Sha256"
|
|
515
|
-
FramesEncoded.$: "$.Payload.FramesEncoded"
|
|
516
|
-
DurationMs.$: "$.Payload.DurationMs"
|
|
517
|
-
Retry:
|
|
518
|
-
- ErrorEquals:
|
|
519
|
-
- FFMPEG_VERSION_MISMATCH
|
|
520
|
-
- PLAN_HASH_MISMATCH
|
|
521
|
-
- BROWSER_GPU_NOT_SOFTWARE
|
|
522
|
-
- PLAN_TOO_LARGE
|
|
523
|
-
- PlanTooLargeError
|
|
524
|
-
- PLAN_PROTOCOL_UNSUPPORTED
|
|
525
|
-
- PlanProtocolUnsupportedError
|
|
526
|
-
- INVALID_VIDEO_METADATA
|
|
527
|
-
- PLAN_ARTIFACT_DIGEST_MISMATCH
|
|
528
|
-
MaxAttempts: 0
|
|
529
|
-
- ErrorEquals: [Lambda.TooManyRequestsException]
|
|
530
|
-
IntervalSeconds: 5
|
|
531
|
-
MaxAttempts: 10
|
|
532
|
-
BackoffRate: 2
|
|
533
|
-
MaxDelaySeconds: 60
|
|
534
|
-
JitterStrategy: FULL
|
|
535
|
-
- ErrorEquals: [States.ALL]
|
|
536
|
-
IntervalSeconds: 2
|
|
537
|
-
MaxAttempts: 4
|
|
538
|
-
BackoffRate: 2
|
|
539
|
-
MaxDelaySeconds: 60
|
|
540
|
-
End: true
|
|
541
|
-
Next: Assemble
|
|
542
|
-
|
|
543
|
-
Assemble:
|
|
544
|
-
Type: Task
|
|
545
|
-
Resource: arn:aws:states:::lambda:invoke
|
|
546
|
-
Parameters:
|
|
547
|
-
FunctionName: !GetAtt RenderFunction.Arn
|
|
548
|
-
Payload:
|
|
549
|
-
Action: assemble
|
|
550
|
-
PlanProtocol: v1
|
|
551
|
-
PlanS3Uri.$: "$.Plan.PlanS3Uri"
|
|
552
|
-
ChunkS3Uris.$: "$.Chunks[*].ChunkS3Uri"
|
|
553
|
-
AudioS3Uri.$: "$.Plan.AudioS3Uri"
|
|
554
|
-
OutputS3Uri.$: "$.OutputS3Uri"
|
|
555
|
-
Format.$: "$.Plan.Format"
|
|
556
|
-
ResultSelector:
|
|
557
|
-
OutputS3Uri.$: "$.Payload.OutputS3Uri"
|
|
558
|
-
FramesEncoded.$: "$.Payload.FramesEncoded"
|
|
559
|
-
FileSize.$: "$.Payload.FileSize"
|
|
560
|
-
ResultPath: $.Output
|
|
561
|
-
Retry:
|
|
562
|
-
- ErrorEquals:
|
|
563
|
-
# Same non-retryable error names as the Plan state's
|
|
564
|
-
# gate — these surface at assemble time too because
|
|
565
|
-
# ffmpeg-driven concat picks up version drift and we
|
|
566
|
-
# re-verify plan hash + format at assemble. Skip the
|
|
567
|
-
# retry storm; fail fast.
|
|
568
|
-
- FFMPEG_VERSION_MISMATCH
|
|
569
|
-
- PLAN_HASH_MISMATCH
|
|
570
|
-
- FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED
|
|
571
|
-
- PLAN_TOO_LARGE
|
|
572
|
-
- PlanTooLargeError
|
|
573
|
-
- PLAN_PROTOCOL_UNSUPPORTED
|
|
574
|
-
- PlanProtocolUnsupportedError
|
|
575
|
-
- PLAN_ARTIFACT_DIGEST_MISMATCH
|
|
576
|
-
MaxAttempts: 0
|
|
577
|
-
- ErrorEquals: [Lambda.TooManyRequestsException]
|
|
578
|
-
IntervalSeconds: 5
|
|
579
|
-
MaxAttempts: 10
|
|
580
|
-
BackoffRate: 2
|
|
581
|
-
MaxDelaySeconds: 60
|
|
582
|
-
JitterStrategy: FULL
|
|
583
|
-
- ErrorEquals: [States.ALL]
|
|
584
|
-
IntervalSeconds: 2
|
|
585
|
-
MaxAttempts: 4
|
|
586
|
-
BackoffRate: 2
|
|
587
|
-
MaxDelaySeconds: 60
|
|
588
|
-
End: true
|
|
589
|
-
|
|
590
394
|
RenderV2:
|
|
591
395
|
# Chunks render video only while the audio task mixes the track AssembleV2 stitches in.
|
|
592
396
|
Type: Parallel
|
|
@@ -644,8 +448,6 @@ Resources:
|
|
|
644
448
|
- PLAN_HASH_MISMATCH
|
|
645
449
|
- S3_URI_NOT_ALLOWED
|
|
646
450
|
- BROWSER_GPU_NOT_SOFTWARE
|
|
647
|
-
- PLAN_TOO_LARGE
|
|
648
|
-
- PlanTooLargeError
|
|
649
451
|
- PLAN_PROTOCOL_UNSUPPORTED
|
|
650
452
|
- PlanProtocolUnsupportedError
|
|
651
453
|
- PLAN_V2_INTEGRITY_UNRECOVERABLE
|
|
@@ -705,8 +507,6 @@ Resources:
|
|
|
705
507
|
- S3_URI_NOT_ALLOWED
|
|
706
508
|
- BROWSER_GPU_NOT_SOFTWARE
|
|
707
509
|
- FONT_FETCH_FAILED
|
|
708
|
-
- PLAN_TOO_LARGE
|
|
709
|
-
- PlanTooLargeError
|
|
710
510
|
- PLAN_PROTOCOL_UNSUPPORTED
|
|
711
511
|
- PlanProtocolUnsupportedError
|
|
712
512
|
- PLAN_V2_INTEGRITY_UNRECOVERABLE
|
|
@@ -765,8 +565,6 @@ Resources:
|
|
|
765
565
|
- PLAN_HASH_MISMATCH
|
|
766
566
|
- S3_URI_NOT_ALLOWED
|
|
767
567
|
- FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED
|
|
768
|
-
- PLAN_TOO_LARGE
|
|
769
|
-
- PlanTooLargeError
|
|
770
568
|
- PLAN_PROTOCOL_UNSUPPORTED
|
|
771
569
|
- PlanProtocolUnsupportedError
|
|
772
570
|
- PLAN_V2_INTEGRITY_UNRECOVERABLE
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
3
|
-
"url": "https://github.com/heygen-com/hyperframes/releases/download/v0.
|
|
4
|
-
"sha256": "
|
|
5
|
-
"bytes":
|
|
2
|
+
"version": "0.9.0",
|
|
3
|
+
"url": "https://github.com/heygen-com/hyperframes/releases/download/v0.9.0/hyperframes-lambda-video-arm64.oci.tar",
|
|
4
|
+
"sha256": "430f853e2654cf21c442322812a8d00efb4c8f10dea3d96e7fceb497ccbbee1b",
|
|
5
|
+
"bytes": 244900352
|
|
6
6
|
}
|