@skyphusion-labs/vivijure-core 1.23.0 → 1.24.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/dist/assemble-admission.d.ts +163 -0
- package/dist/assemble-admission.d.ts.map +1 -0
- package/dist/assemble-admission.js +189 -0
- package/dist/assemble-admission.js.map +1 -0
- package/dist/film-orchestrator.d.ts +14 -0
- package/dist/film-orchestrator.d.ts.map +1 -1
- package/dist/film-orchestrator.js +74 -0
- package/dist/film-orchestrator.js.map +1 -1
- package/dist/modules/conformance.d.ts +16 -1
- package/dist/modules/conformance.d.ts.map +1 -1
- package/dist/modules/conformance.js +31 -5
- package/dist/modules/conformance.js.map +1 -1
- package/dist/modules/types.d.ts +137 -2
- package/dist/modules/types.d.ts.map +1 -1
- package/dist/modules/types.js +53 -4
- package/dist/modules/types.js.map +1 -1
- package/dist/render-orchestrator.d.ts +21 -0
- package/dist/render-orchestrator.d.ts.map +1 -1
- package/dist/render-orchestrator.js +25 -2
- package/dist/render-orchestrator.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-flight input-bytes admission check for a chunked assemble (cf#815).
|
|
3
|
+
*
|
|
4
|
+
* WHAT THIS BOUNDS, AND WHY IT IS NOT AN OUTPUT CAP. The container writes the assembled film to
|
|
5
|
+
* local disk before streaming it to R2, and nothing bounds that write. Chunked assemble (cf#784)
|
|
6
|
+
* flattened peak disk for the BATCH stage -- each batch dir is removed on flush and every partial
|
|
7
|
+
* goes straight to R2 -- but it left the FINALIZE stage untouched:
|
|
8
|
+
*
|
|
9
|
+
* containers/video-finish/app.py:315 `_silent.mp4` <- the joined film
|
|
10
|
+
* containers/video-finish/app.py:786 `final.mp4` <- _mux_bed_onto, written FROM _silent.mp4
|
|
11
|
+
*
|
|
12
|
+
* `_silent.mp4` is never removed (the only `os.remove` calls in the chunked path are the two that
|
|
13
|
+
* reap batch partials, app.py:266 and :270), and the no-bed branch still writes `final.mp4` with
|
|
14
|
+
* `-c copy` rather than renaming. So finalize holds TWO full-length copies of the film at once.
|
|
15
|
+
* That is the term this check bounds. NOTE: the container's own comment at app.py:57-59 says
|
|
16
|
+
* "peak disk at that stage is one film"; the code above says two, and this file follows the code.
|
|
17
|
+
*
|
|
18
|
+
* WHY THE THRESHOLD IS NOT `MAX_CLIPS x MAX_CLIP_BYTES`. cf#813 measures that product (80 x 256 MB
|
|
19
|
+
* = 20.0 GB) as 23x to 120x above what a real film weighs, because `MAX_CLIP_BYTES` is the size at
|
|
20
|
+
* which a DOWNLOAD is refused, not a payload. A gate written against it could never fire, which is
|
|
21
|
+
* worse than no gate because it reads as protection. It is also the wrong QUANTITY: cf#813 measures
|
|
22
|
+
* the normalized-to-source byte ratio at 0.98x to 5.09x, so source bytes do not predict the film.
|
|
23
|
+
*
|
|
24
|
+
* WHAT IT IS WRITTEN AGAINST INSTEAD: bitrate x duration, from cf#813's measured encodes through
|
|
25
|
+
* this container's exact normalize command. See NORMALIZED_BITS_PER_PIXEL_FRAME.
|
|
26
|
+
*
|
|
27
|
+
* WHERE IT RUNS. On the Worker, before the first presign is minted. The Worker has the clip list
|
|
28
|
+
* and the per-clip measured durations for free; the container learns a clip's length only by
|
|
29
|
+
* downloading it. Refusing here costs nothing, refusing there costs the whole download.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Normalized video bits per pixel per frame at the container's encode settings
|
|
33
|
+
* (`libx264 -crf 18 -preset medium`, containers/video-finish/app.py:612).
|
|
34
|
+
*
|
|
35
|
+
* DERIVED FROM cf#813's MEASUREMENT, not picked. Its 1080p24 band tops out at 11.16 Mbps:
|
|
36
|
+
*
|
|
37
|
+
* 11.16e6 bits/s / (1920 x 1080 x 24 px/s) = 0.2243 bits/px/frame
|
|
38
|
+
*
|
|
39
|
+
* Rounded UP to 0.225. Cross-checked against the same measurement's 720p24 ceiling, which is an
|
|
40
|
+
* independent row: 0.225 x 1280 x 720 x 24 = 4.98 Mbps against a measured 4.82 Mbps, i.e. 3.3%
|
|
41
|
+
* conservative. Bits-per-pixel being near-constant across the two resolutions is what lets one
|
|
42
|
+
* number serve any delivery target instead of a per-resolution table that would rot.
|
|
43
|
+
*
|
|
44
|
+
* THE CEILING OF THE BAND, DELIBERATELY, and the direction of error is the point. This predicts
|
|
45
|
+
* the LARGEST film a given input can normalize to, so the refusal comes early rather than after
|
|
46
|
+
* the disk is already full. cf#813 also warns that its pan-based proxies UNDERESTIMATE real
|
|
47
|
+
* generated video (no subject deformation, no per-frame diffusion shimmer), which argues for the
|
|
48
|
+
* ceiling rather than the middle of the band as well.
|
|
49
|
+
*
|
|
50
|
+
* A film's predicted bytes are therefore an upper bound on its size and a lower bound on nothing:
|
|
51
|
+
* a legitimate film lands well under, which is what keeps the CONTROL case admitted.
|
|
52
|
+
*/
|
|
53
|
+
export declare const NORMALIZED_BITS_PER_PIXEL_FRAME = 0.225;
|
|
54
|
+
/**
|
|
55
|
+
* Ephemeral disk of the container instance the finish tier runs on.
|
|
56
|
+
*
|
|
57
|
+
* `instance_type = "standard-4"` (vivijure-cf wrangler.toml.example:238), documented there at
|
|
58
|
+
* :201 as "the LARGEST Cloudflare offers (4 vCPU / 12 GiB / 20 GB disk)". Read as DECIMAL GB, the
|
|
59
|
+
* smaller of the two readings, because guessing GiB here would silently widen the budget by 7%.
|
|
60
|
+
*/
|
|
61
|
+
export declare const CONTAINER_DISK_BYTES = 20000000000;
|
|
62
|
+
/**
|
|
63
|
+
* Full-length copies of the film resident on disk at once during finalize.
|
|
64
|
+
*
|
|
65
|
+
* TWO, not one: `_silent.mp4` then `final.mp4`, with no remove between them. See the file header
|
|
66
|
+
* for the exact lines. This is the multiplier the whole check turns on, so it is a named constant
|
|
67
|
+
* with its evidence rather than a `* 2` inline.
|
|
68
|
+
*/
|
|
69
|
+
export declare const FINALIZE_FILM_COPIES = 2;
|
|
70
|
+
/**
|
|
71
|
+
* Peak disk the BATCH stage may occupy, mirroring the container's own budget at
|
|
72
|
+
* containers/video-finish/app.py:49-50: `3 x (MAX_BATCH_BYTES + MAX_CLIP_BYTES)` -- a batch's
|
|
73
|
+
* sources, their normalized copies, and the batch partial.
|
|
74
|
+
*
|
|
75
|
+
* Reserved even though the batch dirs are removed before the final join, so the two stages do not
|
|
76
|
+
* in fact coexist. cf#815 asks for headroom for "the assembled film plus one batch working set",
|
|
77
|
+
* and a reserve that survives a future reordering of those stages is worth 4 GB of a 20 GB disk.
|
|
78
|
+
*
|
|
79
|
+
* cf#813 argues this multiplier should be ~5x rather than 3x against SOURCE bytes, because the
|
|
80
|
+
* normalized copy is roughly 2x its source while the fixed 1920x1080 target is still upscaling.
|
|
81
|
+
* Correcting the container's constant is cf#813's acceptance item, not this one's; this reserve
|
|
82
|
+
* deliberately quotes the container's CURRENT number so the two cannot silently disagree about
|
|
83
|
+
* what is reserved today.
|
|
84
|
+
*/
|
|
85
|
+
export declare const CONTAINER_BATCH_PEAK_BYTES: number;
|
|
86
|
+
/** `MAX_AUDIO_BYTES`, containers/video-finish/app.py:41. The bed lands on disk beside the film. */
|
|
87
|
+
export declare const CONTAINER_AUDIO_BED_BYTES: number;
|
|
88
|
+
/**
|
|
89
|
+
* Disk the image, the OS and ffmpeg's own scratch occupy, which the film must not be allowed to
|
|
90
|
+
* claim. A RESERVE, stated as one: it is not a measurement of the image, and it is deliberately
|
|
91
|
+
* generous, because the cost of over-reserving is a lower film ceiling and the cost of
|
|
92
|
+
* under-reserving is the ENOSPC this check exists to prevent.
|
|
93
|
+
*/
|
|
94
|
+
export declare const CONTAINER_IMAGE_RESERVE_BYTES = 2000000000;
|
|
95
|
+
/**
|
|
96
|
+
* Frames per second the container normalizes to when the payload omits `fps`.
|
|
97
|
+
*
|
|
98
|
+
* `int(body.get("fps", 24))`, containers/video-finish/app.py:391 (and :205, :1107).
|
|
99
|
+
*
|
|
100
|
+
* THREE NUMBERS HAVE TO AGREE HERE and a comment cannot make them: the container's fallback, core's
|
|
101
|
+
* `DEFAULT_DELIVERY_FPS` (film-model.ts:1458), and this prediction. `assemble-admission.test.ts`
|
|
102
|
+
* asserts this constant equals `DEFAULT_DELIVERY_FPS`, so a future edit to either one fails a test
|
|
103
|
+
* rather than silently mispredicting every film by the ratio between them.
|
|
104
|
+
*/
|
|
105
|
+
export declare const CONTAINER_DEFAULT_FPS = 24;
|
|
106
|
+
/** The largest predicted film the container can finalize on its ephemeral disk. */
|
|
107
|
+
export declare function assembleFilmByteCeiling(): number;
|
|
108
|
+
/** Predicted normalized bytes for `seconds` of video at a delivery target. */
|
|
109
|
+
export declare function predictNormalizedBytes(width: number, height: number, fps: number, seconds: number): number;
|
|
110
|
+
/** Seconds of film this assemble will produce, and how much of that is EVIDENCE. */
|
|
111
|
+
export interface AssembleSecondsBasis {
|
|
112
|
+
/** Summed seconds of every clip whose length is known. */
|
|
113
|
+
seconds: number;
|
|
114
|
+
/** Clips whose length came from the container's own probe (delivered_frames / delivered_fps). */
|
|
115
|
+
measured: number;
|
|
116
|
+
/** Clips whose length came from the plan (bundle target_seconds, else the authored scene). */
|
|
117
|
+
planned: number;
|
|
118
|
+
/** Clips with neither. They contribute ZERO seconds, so `seconds` is a LOWER bound. */
|
|
119
|
+
unknown: number;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Resolve the film's length from what the Worker already holds.
|
|
123
|
+
*
|
|
124
|
+
* MEASURED WINS, PLAN FILLS, ABSENCE IS NAMED. This mirrors the #697 duration gate's discipline
|
|
125
|
+
* (`findClipDurationShortfalls`): the check fires on EVIDENCE and never on absence, so a clip with
|
|
126
|
+
* no known length cannot by itself refuse a film. It contributes zero seconds and is COUNTED, so
|
|
127
|
+
* the caller can say the prediction is a lower bound instead of implying it is complete.
|
|
128
|
+
*
|
|
129
|
+
* A lower bound is still enough to refuse on: if the clips we CAN size already exceed the disk,
|
|
130
|
+
* the ones we cannot size do not make the film smaller.
|
|
131
|
+
*/
|
|
132
|
+
export declare function resolveAssembleSeconds(finalClips: {
|
|
133
|
+
shot_id: string;
|
|
134
|
+
}[], measuredSeconds: Map<string, number> | ReadonlyMap<string, number>, plannedSeconds: Record<string, number>): AssembleSecondsBasis;
|
|
135
|
+
export interface AssembleAdmissionInput {
|
|
136
|
+
clipCount: number;
|
|
137
|
+
width: number;
|
|
138
|
+
height: number;
|
|
139
|
+
fps?: number;
|
|
140
|
+
basis: AssembleSecondsBasis;
|
|
141
|
+
}
|
|
142
|
+
export interface AssembleAdmission {
|
|
143
|
+
admitted: boolean;
|
|
144
|
+
predictedBytes: number;
|
|
145
|
+
ceilingBytes: number;
|
|
146
|
+
/** Seconds of film the ceiling allows at this delivery target. The actionable number. */
|
|
147
|
+
allowedSeconds: number;
|
|
148
|
+
basis: AssembleSecondsBasis;
|
|
149
|
+
/** Loud, arithmetic-bearing refusal. Set only when `admitted` is false. */
|
|
150
|
+
reason?: string;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Admit or refuse an assemble before a single byte is fetched.
|
|
154
|
+
*
|
|
155
|
+
* PURE. Every number it uses is either passed in or a constant above with its provenance, so the
|
|
156
|
+
* arithmetic in the refusal is reproducible from the message alone.
|
|
157
|
+
*
|
|
158
|
+
* REFUSAL IS LOUD AND TERMINAL, never a degrade (cf#815, the #249/#77 discipline). A film that
|
|
159
|
+
* cannot fit is not silently truncated to one that does: the caller is told what was asked, what
|
|
160
|
+
* fits, by how much it is over, and what length would be accepted.
|
|
161
|
+
*/
|
|
162
|
+
export declare function admitAssemble(input: AssembleAdmissionInput): AssembleAdmission;
|
|
163
|
+
//# sourceMappingURL=assemble-admission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assemble-admission.d.ts","sourceRoot":"","sources":["../src/assemble-admission.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,+BAA+B,QAAQ,CAAC;AAErD;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,cAAiB,CAAC;AAEnD;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,0BAA0B,QAA+C,CAAC;AAEvF,mGAAmG;AACnG,eAAO,MAAM,yBAAyB,QAAoB,CAAC;AAE3D;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,aAAgB,CAAC;AAE3D;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC,mFAAmF;AACnF,wBAAgB,uBAAuB,IAAI,MAAM,CAIhD;AAED,8EAA8E;AAC9E,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,GACd,MAAM,CAGR;AAED,oFAAoF;AACpF,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,iGAAiG;IACjG,QAAQ,EAAE,MAAM,CAAC;IACjB,8FAA8F;IAC9F,OAAO,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,EACjC,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAClE,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACrC,oBAAoB,CAsBtB;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,oBAAoB,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,yFAAyF;IACzF,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,oBAAoB,CAAC;IAC5B,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAMD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,sBAAsB,GAAG,iBAAiB,CAwB9E"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-flight input-bytes admission check for a chunked assemble (cf#815).
|
|
3
|
+
*
|
|
4
|
+
* WHAT THIS BOUNDS, AND WHY IT IS NOT AN OUTPUT CAP. The container writes the assembled film to
|
|
5
|
+
* local disk before streaming it to R2, and nothing bounds that write. Chunked assemble (cf#784)
|
|
6
|
+
* flattened peak disk for the BATCH stage -- each batch dir is removed on flush and every partial
|
|
7
|
+
* goes straight to R2 -- but it left the FINALIZE stage untouched:
|
|
8
|
+
*
|
|
9
|
+
* containers/video-finish/app.py:315 `_silent.mp4` <- the joined film
|
|
10
|
+
* containers/video-finish/app.py:786 `final.mp4` <- _mux_bed_onto, written FROM _silent.mp4
|
|
11
|
+
*
|
|
12
|
+
* `_silent.mp4` is never removed (the only `os.remove` calls in the chunked path are the two that
|
|
13
|
+
* reap batch partials, app.py:266 and :270), and the no-bed branch still writes `final.mp4` with
|
|
14
|
+
* `-c copy` rather than renaming. So finalize holds TWO full-length copies of the film at once.
|
|
15
|
+
* That is the term this check bounds. NOTE: the container's own comment at app.py:57-59 says
|
|
16
|
+
* "peak disk at that stage is one film"; the code above says two, and this file follows the code.
|
|
17
|
+
*
|
|
18
|
+
* WHY THE THRESHOLD IS NOT `MAX_CLIPS x MAX_CLIP_BYTES`. cf#813 measures that product (80 x 256 MB
|
|
19
|
+
* = 20.0 GB) as 23x to 120x above what a real film weighs, because `MAX_CLIP_BYTES` is the size at
|
|
20
|
+
* which a DOWNLOAD is refused, not a payload. A gate written against it could never fire, which is
|
|
21
|
+
* worse than no gate because it reads as protection. It is also the wrong QUANTITY: cf#813 measures
|
|
22
|
+
* the normalized-to-source byte ratio at 0.98x to 5.09x, so source bytes do not predict the film.
|
|
23
|
+
*
|
|
24
|
+
* WHAT IT IS WRITTEN AGAINST INSTEAD: bitrate x duration, from cf#813's measured encodes through
|
|
25
|
+
* this container's exact normalize command. See NORMALIZED_BITS_PER_PIXEL_FRAME.
|
|
26
|
+
*
|
|
27
|
+
* WHERE IT RUNS. On the Worker, before the first presign is minted. The Worker has the clip list
|
|
28
|
+
* and the per-clip measured durations for free; the container learns a clip's length only by
|
|
29
|
+
* downloading it. Refusing here costs nothing, refusing there costs the whole download.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Normalized video bits per pixel per frame at the container's encode settings
|
|
33
|
+
* (`libx264 -crf 18 -preset medium`, containers/video-finish/app.py:612).
|
|
34
|
+
*
|
|
35
|
+
* DERIVED FROM cf#813's MEASUREMENT, not picked. Its 1080p24 band tops out at 11.16 Mbps:
|
|
36
|
+
*
|
|
37
|
+
* 11.16e6 bits/s / (1920 x 1080 x 24 px/s) = 0.2243 bits/px/frame
|
|
38
|
+
*
|
|
39
|
+
* Rounded UP to 0.225. Cross-checked against the same measurement's 720p24 ceiling, which is an
|
|
40
|
+
* independent row: 0.225 x 1280 x 720 x 24 = 4.98 Mbps against a measured 4.82 Mbps, i.e. 3.3%
|
|
41
|
+
* conservative. Bits-per-pixel being near-constant across the two resolutions is what lets one
|
|
42
|
+
* number serve any delivery target instead of a per-resolution table that would rot.
|
|
43
|
+
*
|
|
44
|
+
* THE CEILING OF THE BAND, DELIBERATELY, and the direction of error is the point. This predicts
|
|
45
|
+
* the LARGEST film a given input can normalize to, so the refusal comes early rather than after
|
|
46
|
+
* the disk is already full. cf#813 also warns that its pan-based proxies UNDERESTIMATE real
|
|
47
|
+
* generated video (no subject deformation, no per-frame diffusion shimmer), which argues for the
|
|
48
|
+
* ceiling rather than the middle of the band as well.
|
|
49
|
+
*
|
|
50
|
+
* A film's predicted bytes are therefore an upper bound on its size and a lower bound on nothing:
|
|
51
|
+
* a legitimate film lands well under, which is what keeps the CONTROL case admitted.
|
|
52
|
+
*/
|
|
53
|
+
export const NORMALIZED_BITS_PER_PIXEL_FRAME = 0.225;
|
|
54
|
+
/**
|
|
55
|
+
* Ephemeral disk of the container instance the finish tier runs on.
|
|
56
|
+
*
|
|
57
|
+
* `instance_type = "standard-4"` (vivijure-cf wrangler.toml.example:238), documented there at
|
|
58
|
+
* :201 as "the LARGEST Cloudflare offers (4 vCPU / 12 GiB / 20 GB disk)". Read as DECIMAL GB, the
|
|
59
|
+
* smaller of the two readings, because guessing GiB here would silently widen the budget by 7%.
|
|
60
|
+
*/
|
|
61
|
+
export const CONTAINER_DISK_BYTES = 20_000_000_000;
|
|
62
|
+
/**
|
|
63
|
+
* Full-length copies of the film resident on disk at once during finalize.
|
|
64
|
+
*
|
|
65
|
+
* TWO, not one: `_silent.mp4` then `final.mp4`, with no remove between them. See the file header
|
|
66
|
+
* for the exact lines. This is the multiplier the whole check turns on, so it is a named constant
|
|
67
|
+
* with its evidence rather than a `* 2` inline.
|
|
68
|
+
*/
|
|
69
|
+
export const FINALIZE_FILM_COPIES = 2;
|
|
70
|
+
/**
|
|
71
|
+
* Peak disk the BATCH stage may occupy, mirroring the container's own budget at
|
|
72
|
+
* containers/video-finish/app.py:49-50: `3 x (MAX_BATCH_BYTES + MAX_CLIP_BYTES)` -- a batch's
|
|
73
|
+
* sources, their normalized copies, and the batch partial.
|
|
74
|
+
*
|
|
75
|
+
* Reserved even though the batch dirs are removed before the final join, so the two stages do not
|
|
76
|
+
* in fact coexist. cf#815 asks for headroom for "the assembled film plus one batch working set",
|
|
77
|
+
* and a reserve that survives a future reordering of those stages is worth 4 GB of a 20 GB disk.
|
|
78
|
+
*
|
|
79
|
+
* cf#813 argues this multiplier should be ~5x rather than 3x against SOURCE bytes, because the
|
|
80
|
+
* normalized copy is roughly 2x its source while the fixed 1920x1080 target is still upscaling.
|
|
81
|
+
* Correcting the container's constant is cf#813's acceptance item, not this one's; this reserve
|
|
82
|
+
* deliberately quotes the container's CURRENT number so the two cannot silently disagree about
|
|
83
|
+
* what is reserved today.
|
|
84
|
+
*/
|
|
85
|
+
export const CONTAINER_BATCH_PEAK_BYTES = 3 * (1024 * 1024 * 1024 + 256 * 1024 * 1024);
|
|
86
|
+
/** `MAX_AUDIO_BYTES`, containers/video-finish/app.py:41. The bed lands on disk beside the film. */
|
|
87
|
+
export const CONTAINER_AUDIO_BED_BYTES = 256 * 1024 * 1024;
|
|
88
|
+
/**
|
|
89
|
+
* Disk the image, the OS and ffmpeg's own scratch occupy, which the film must not be allowed to
|
|
90
|
+
* claim. A RESERVE, stated as one: it is not a measurement of the image, and it is deliberately
|
|
91
|
+
* generous, because the cost of over-reserving is a lower film ceiling and the cost of
|
|
92
|
+
* under-reserving is the ENOSPC this check exists to prevent.
|
|
93
|
+
*/
|
|
94
|
+
export const CONTAINER_IMAGE_RESERVE_BYTES = 2_000_000_000;
|
|
95
|
+
/**
|
|
96
|
+
* Frames per second the container normalizes to when the payload omits `fps`.
|
|
97
|
+
*
|
|
98
|
+
* `int(body.get("fps", 24))`, containers/video-finish/app.py:391 (and :205, :1107).
|
|
99
|
+
*
|
|
100
|
+
* THREE NUMBERS HAVE TO AGREE HERE and a comment cannot make them: the container's fallback, core's
|
|
101
|
+
* `DEFAULT_DELIVERY_FPS` (film-model.ts:1458), and this prediction. `assemble-admission.test.ts`
|
|
102
|
+
* asserts this constant equals `DEFAULT_DELIVERY_FPS`, so a future edit to either one fails a test
|
|
103
|
+
* rather than silently mispredicting every film by the ratio between them.
|
|
104
|
+
*/
|
|
105
|
+
export const CONTAINER_DEFAULT_FPS = 24;
|
|
106
|
+
/** The largest predicted film the container can finalize on its ephemeral disk. */
|
|
107
|
+
export function assembleFilmByteCeiling() {
|
|
108
|
+
const reserved = CONTAINER_BATCH_PEAK_BYTES + CONTAINER_AUDIO_BED_BYTES + CONTAINER_IMAGE_RESERVE_BYTES;
|
|
109
|
+
return Math.max(0, Math.floor((CONTAINER_DISK_BYTES - reserved) / FINALIZE_FILM_COPIES));
|
|
110
|
+
}
|
|
111
|
+
/** Predicted normalized bytes for `seconds` of video at a delivery target. */
|
|
112
|
+
export function predictNormalizedBytes(width, height, fps, seconds) {
|
|
113
|
+
if (!(width > 0) || !(height > 0) || !(fps > 0) || !(seconds > 0))
|
|
114
|
+
return 0;
|
|
115
|
+
return Math.ceil((NORMALIZED_BITS_PER_PIXEL_FRAME * width * height * fps * seconds) / 8);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Resolve the film's length from what the Worker already holds.
|
|
119
|
+
*
|
|
120
|
+
* MEASURED WINS, PLAN FILLS, ABSENCE IS NAMED. This mirrors the #697 duration gate's discipline
|
|
121
|
+
* (`findClipDurationShortfalls`): the check fires on EVIDENCE and never on absence, so a clip with
|
|
122
|
+
* no known length cannot by itself refuse a film. It contributes zero seconds and is COUNTED, so
|
|
123
|
+
* the caller can say the prediction is a lower bound instead of implying it is complete.
|
|
124
|
+
*
|
|
125
|
+
* A lower bound is still enough to refuse on: if the clips we CAN size already exceed the disk,
|
|
126
|
+
* the ones we cannot size do not make the film smaller.
|
|
127
|
+
*/
|
|
128
|
+
export function resolveAssembleSeconds(finalClips, measuredSeconds, plannedSeconds) {
|
|
129
|
+
let seconds = 0;
|
|
130
|
+
let measured = 0;
|
|
131
|
+
let planned = 0;
|
|
132
|
+
let unknown = 0;
|
|
133
|
+
for (const c of finalClips ?? []) {
|
|
134
|
+
if (!c || typeof c.shot_id !== "string")
|
|
135
|
+
continue;
|
|
136
|
+
const m = measuredSeconds?.get(c.shot_id);
|
|
137
|
+
if (typeof m === "number" && Number.isFinite(m) && m > 0) {
|
|
138
|
+
seconds += m;
|
|
139
|
+
measured += 1;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const p = plannedSeconds?.[c.shot_id];
|
|
143
|
+
if (typeof p === "number" && Number.isFinite(p) && p > 0) {
|
|
144
|
+
seconds += p;
|
|
145
|
+
planned += 1;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
unknown += 1;
|
|
149
|
+
}
|
|
150
|
+
return { seconds, measured, planned, unknown };
|
|
151
|
+
}
|
|
152
|
+
function gb(bytes) {
|
|
153
|
+
return `${(bytes / 1_000_000_000).toFixed(2)} GB`;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Admit or refuse an assemble before a single byte is fetched.
|
|
157
|
+
*
|
|
158
|
+
* PURE. Every number it uses is either passed in or a constant above with its provenance, so the
|
|
159
|
+
* arithmetic in the refusal is reproducible from the message alone.
|
|
160
|
+
*
|
|
161
|
+
* REFUSAL IS LOUD AND TERMINAL, never a degrade (cf#815, the #249/#77 discipline). A film that
|
|
162
|
+
* cannot fit is not silently truncated to one that does: the caller is told what was asked, what
|
|
163
|
+
* fits, by how much it is over, and what length would be accepted.
|
|
164
|
+
*/
|
|
165
|
+
export function admitAssemble(input) {
|
|
166
|
+
const fps = input.fps && input.fps > 0 ? input.fps : CONTAINER_DEFAULT_FPS;
|
|
167
|
+
const { width, height, basis, clipCount } = input;
|
|
168
|
+
const ceilingBytes = assembleFilmByteCeiling();
|
|
169
|
+
const predictedBytes = predictNormalizedBytes(width, height, fps, basis.seconds);
|
|
170
|
+
const perSecond = predictNormalizedBytes(width, height, fps, 1);
|
|
171
|
+
const allowedSeconds = perSecond > 0 ? Math.floor(ceilingBytes / perSecond) : 0;
|
|
172
|
+
if (predictedBytes <= ceilingBytes) {
|
|
173
|
+
return { admitted: true, predictedBytes, ceilingBytes, allowedSeconds, basis };
|
|
174
|
+
}
|
|
175
|
+
const over = predictedBytes - ceilingBytes;
|
|
176
|
+
const bound = basis.unknown > 0 ? " (a LOWER bound: " + basis.unknown + " clip(s) of unknown length contributed 0s)" : "";
|
|
177
|
+
const reason = `assemble refused before any clip was fetched: ${clipCount} clip(s) totalling ` +
|
|
178
|
+
`${basis.seconds.toFixed(1)}s at ${width}x${height}@${fps} predict ${gb(predictedBytes)} of ` +
|
|
179
|
+
`normalized video${bound}, and the container can finalize at most ${gb(ceilingBytes)}. ` +
|
|
180
|
+
`Over by ${gb(over)}. Budget: ${gb(CONTAINER_DISK_BYTES)} ephemeral disk (standard-4), less ` +
|
|
181
|
+
`${gb(CONTAINER_BATCH_PEAK_BYTES)} batch working set, ${gb(CONTAINER_AUDIO_BED_BYTES)} audio ` +
|
|
182
|
+
`bed and ${gb(CONTAINER_IMAGE_RESERVE_BYTES)} image reserve, divided by ` +
|
|
183
|
+
`${FINALIZE_FILM_COPIES} full-length copies resident at finalize. ` +
|
|
184
|
+
`Basis: ${basis.measured} measured, ${basis.planned} planned, ${basis.unknown} unknown. ` +
|
|
185
|
+
`This delivery target admits at most ${allowedSeconds}s; reduce the film, or lower ` +
|
|
186
|
+
`delivery_width/delivery_height.`;
|
|
187
|
+
return { admitted: false, predictedBytes, ceilingBytes, allowedSeconds, basis, reason };
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=assemble-admission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assemble-admission.js","sourceRoot":"","sources":["../src/assemble-admission.ts"],"names":[],"mappings":"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"}
|
|
@@ -64,6 +64,20 @@ export declare function measuredClipDimensions(env: Env, job: FilmJob): Promise<
|
|
|
64
64
|
width: number;
|
|
65
65
|
height: number;
|
|
66
66
|
}>>;
|
|
67
|
+
/**
|
|
68
|
+
* Per-shot MEASURED clip seconds, from the clip doc the render already wrote.
|
|
69
|
+
*
|
|
70
|
+
* Sibling of `measuredClipDimensions` above, reading the same single R2 object: `delivered_frames`
|
|
71
|
+
* and `delivered_fps` are persisted on every done clip by `validateDoneClips`
|
|
72
|
+
* (render-orchestrator.ts:163-164) from the container's own probe. `frames / fps` is therefore a
|
|
73
|
+
* measurement of what the clip IS, not a plan for what it should have been.
|
|
74
|
+
*
|
|
75
|
+
* A MISS IS NOT A DEFAULT, for the same reason as the dimensions sibling. No clip doc, an
|
|
76
|
+
* unparseable doc, a shot with neither field, or a non-positive fps all yield NO ENTRY, and the
|
|
77
|
+
* caller falls back to the PLAN and counts the fallback. Inventing a duration here would put a
|
|
78
|
+
* fabricated number into a refusal message, which is the one place it must not appear.
|
|
79
|
+
*/
|
|
80
|
+
export declare function measuredClipSeconds(env: Env, job: FilmJob): Promise<Map<string, number>>;
|
|
67
81
|
/** Call the video-finish container's POST /finish, retrying on a transient gateway status -- 503 (a
|
|
68
82
|
* cold container can 503 while its port is still binding -- same shape as callImagePrep in
|
|
69
83
|
* bundle-assembler) or 504 (a cold-boot + ffmpeg concat that exceeds the request window; issue #82).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"film-orchestrator.d.ts","sourceRoot":"","sources":["../src/film-orchestrator.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,oCAAoC,CAAC;AAyC9D,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,UAAU,EAGf,KAAK,eAAe,EACpB,KAAK,OAAO,EAkDb,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"film-orchestrator.d.ts","sourceRoot":"","sources":["../src/film-orchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,6 +17,7 @@ import { usageOf } from "./motion-usage.js";
|
|
|
17
17
|
import { LINE_WAV_MIN_SECONDS, mintSilenceWav, normalizeLineWav, secondsForShot, } from "./wav-duration.js";
|
|
18
18
|
import { claimFilmSubmit, naturalKeyForStartFilmJob, naturalKeyForStartFromKeyframes, releaseFilmSubmitClaim, } from "./film-submit-idempotency.js";
|
|
19
19
|
import { filmJobDocKey as filmKey, clipJobDocKey as clipDocKey, orderFinalClips, joinKeyframesToScenes, composeMotionPrompt, spokenLineForShot, applyVoiceSeed, applyFinishOutput, applyFinishOutputOrRefuse, adoptFinishStepOutput, applySpeechOutput, FINISH_STEP_MAX_ATTEMPTS, classifyFinishFailure, classifyFinishRetry, resolveFinishConfigs, finishShotAdoptableFromR2, finishShotRecoverable, finishPhaseBlockers, reclaimFinishShotsFromR2, finishStepOutputKey, finishStepAppliedTag, finishShotLedgerReconciles, MASTER_STEP_MAX_ATTEMPTS, MASTER_STALL_SECONDS, filmSeconds, masteredBedKey, applyMasterOutput, degradeMasterStep, masterChainDone, coerceSceneIds, coerceDialogueLineIds, resolveKeyframeParallel, partitionContiguous, KEYFRAME_STALL_SECONDS, PHASE_HARD_DEADLINE_SECONDS, phaseCeilingVerdict, POLLABLE_PHASES, phaseAgeSeconds, stampFilmProgress, resolveClipDurationFloor, mapClipDurationsToShots, resolvePlannedSeconds, findClipDurationShortfalls, captionDurations, resolveDeliveryResolution, DEFAULT_DELIVERY_FPS, } from "./film-model.js";
|
|
20
|
+
import { admitAssemble, resolveAssembleSeconds, } from "./assemble-admission.js";
|
|
20
21
|
import { retimeSrt } from "./srt.js";
|
|
21
22
|
import { loadInstallConfig } from "./operator-config.js";
|
|
22
23
|
import { startClipJob, advanceClipJob, cancelInFlightClips, summarizeJob, describeClipFailures, clipFileMatchesShot, finishedClipFileMatchesShot, listClipsByShotId, reclaimClipsFromR2, validateDoneClips, } from "./render-orchestrator.js";
|
|
@@ -1081,6 +1082,40 @@ export async function measuredClipDimensions(env, job) {
|
|
|
1081
1082
|
}
|
|
1082
1083
|
return out;
|
|
1083
1084
|
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Per-shot MEASURED clip seconds, from the clip doc the render already wrote.
|
|
1087
|
+
*
|
|
1088
|
+
* Sibling of `measuredClipDimensions` above, reading the same single R2 object: `delivered_frames`
|
|
1089
|
+
* and `delivered_fps` are persisted on every done clip by `validateDoneClips`
|
|
1090
|
+
* (render-orchestrator.ts:163-164) from the container's own probe. `frames / fps` is therefore a
|
|
1091
|
+
* measurement of what the clip IS, not a plan for what it should have been.
|
|
1092
|
+
*
|
|
1093
|
+
* A MISS IS NOT A DEFAULT, for the same reason as the dimensions sibling. No clip doc, an
|
|
1094
|
+
* unparseable doc, a shot with neither field, or a non-positive fps all yield NO ENTRY, and the
|
|
1095
|
+
* caller falls back to the PLAN and counts the fallback. Inventing a duration here would put a
|
|
1096
|
+
* fabricated number into a refusal message, which is the one place it must not appear.
|
|
1097
|
+
*/
|
|
1098
|
+
export async function measuredClipSeconds(env, job) {
|
|
1099
|
+
const out = new Map();
|
|
1100
|
+
if (!job.clip_job_id)
|
|
1101
|
+
return out;
|
|
1102
|
+
try {
|
|
1103
|
+
const obj = await env.R2_RENDERS.get(clipDocKey(job.clip_job_id));
|
|
1104
|
+
if (!obj)
|
|
1105
|
+
return out;
|
|
1106
|
+
const clipJob = JSON.parse(await obj.text());
|
|
1107
|
+
for (const sh of clipJob.shots || []) {
|
|
1108
|
+
const frames = Number(sh.delivered_frames), fps = Number(sh.delivered_fps);
|
|
1109
|
+
if (Number.isFinite(frames) && frames > 0 && Number.isFinite(fps) && fps > 0) {
|
|
1110
|
+
out.set(sh.shot_id, frames / fps);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
catch {
|
|
1115
|
+
return out;
|
|
1116
|
+
}
|
|
1117
|
+
return out;
|
|
1118
|
+
}
|
|
1084
1119
|
async function advanceFinishPhase(env, job, preModules) {
|
|
1085
1120
|
const envRec = env;
|
|
1086
1121
|
// Read once per pass, not per shot: one GET serves every shot in this dispatch.
|
|
@@ -2306,6 +2341,45 @@ async function enterAssemblePhase(env, job, finalClips, preModules) {
|
|
|
2306
2341
|
height: delivery.height,
|
|
2307
2342
|
};
|
|
2308
2343
|
if (!job.assemble_poll) {
|
|
2344
|
+
// cf#815: PRE-FLIGHT INPUT ADMISSION. Refuse a film that cannot fit the container's ephemeral
|
|
2345
|
+
// disk BEFORE a presign is minted, so nothing is downloaded, normalized or encoded first.
|
|
2346
|
+
//
|
|
2347
|
+
// Inside the `!job.assemble_poll` branch deliberately: this is the one pass that mints URLs and
|
|
2348
|
+
// submits, so it is the only pass where a refusal costs nothing. Later ticks are polls, and
|
|
2349
|
+
// re-gating them would burn two R2 reads each to re-derive an answer that cannot have changed.
|
|
2350
|
+
//
|
|
2351
|
+
// WHY NOT AN OUTPUT CAP, AND WHY NOT `MAX_CLIPS x MAX_CLIP_BYTES`: see src/assemble-admission.ts.
|
|
2352
|
+
// Short form: an output cap rejects after the whole film has been built, and that product is
|
|
2353
|
+
// measured 23x-120x above a real film (cf#813), so a gate written against it could never fire.
|
|
2354
|
+
const [measuredSecs, bundleDurations] = await Promise.all([
|
|
2355
|
+
measuredClipSeconds(env, job),
|
|
2356
|
+
readShotDurationsFromBundle(env, job.bundle_key),
|
|
2357
|
+
]);
|
|
2358
|
+
const admission = admitAssemble({
|
|
2359
|
+
clipCount: finalClips.length,
|
|
2360
|
+
width: delivery.width,
|
|
2361
|
+
height: delivery.height,
|
|
2362
|
+
fps: DEFAULT_DELIVERY_FPS,
|
|
2363
|
+
basis: resolveAssembleSeconds(finalClips, measuredSecs, resolvePlannedSeconds(job.scenes, bundleDurations)),
|
|
2364
|
+
});
|
|
2365
|
+
if (!admission.admitted) {
|
|
2366
|
+
// TERMINAL and LOUD, never a degrade (cf#815, the #249/#77 discipline). Truncating the film to
|
|
2367
|
+
// something that fits would ship a different film than was asked for and call it done.
|
|
2368
|
+
emitStructuredEvent({
|
|
2369
|
+
ev: "assemble.refused",
|
|
2370
|
+
film_id: job.film_id,
|
|
2371
|
+
clips: finalClips.length,
|
|
2372
|
+
seconds: Math.round(admission.basis.seconds * 1000) / 1000,
|
|
2373
|
+
predicted_bytes: admission.predictedBytes,
|
|
2374
|
+
ceiling_bytes: admission.ceilingBytes,
|
|
2375
|
+
allowed_seconds: admission.allowedSeconds,
|
|
2376
|
+
basis: admission.basis,
|
|
2377
|
+
reason: admission.reason,
|
|
2378
|
+
});
|
|
2379
|
+
job.phase = "failed";
|
|
2380
|
+
job.error = admission.reason;
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2309
2383
|
// ASSEMBLE_PRESIGN_TTL_SECONDS, not the old hardcoded 1800. Under chunked assemble batch N
|
|
2310
2384
|
// downloads its clips only after batch N-1 has finished encoding, and the final pass reads
|
|
2311
2385
|
// the FIRST partial at the very END of the job, so every URL here has to cover the whole
|