@skyphusion-labs/vivijure-core 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/LICENSE +661 -0
- package/README.md +87 -0
- package/dist/audio-routing.d.ts +2 -0
- package/dist/audio-routing.d.ts.map +1 -0
- package/dist/audio-routing.js +4 -0
- package/dist/audio-routing.js.map +1 -0
- package/dist/audio-stage.d.ts +4 -0
- package/dist/audio-stage.d.ts.map +1 -0
- package/dist/audio-stage.js +25 -0
- package/dist/audio-stage.js.map +1 -0
- package/dist/beat-analyze.d.ts +22 -0
- package/dist/beat-analyze.d.ts.map +1 -0
- package/dist/beat-analyze.js +111 -0
- package/dist/beat-analyze.js.map +1 -0
- package/dist/beat-sync-types.d.ts +33 -0
- package/dist/beat-sync-types.d.ts.map +1 -0
- package/dist/beat-sync-types.js +4 -0
- package/dist/beat-sync-types.js.map +1 -0
- package/dist/bundle-assembler.d.ts +54 -0
- package/dist/bundle-assembler.d.ts.map +1 -0
- package/dist/bundle-assembler.js +376 -0
- package/dist/bundle-assembler.js.map +1 -0
- package/dist/bundle-durations.d.ts +5 -0
- package/dist/bundle-durations.d.ts.map +1 -0
- package/dist/bundle-durations.js +68 -0
- package/dist/bundle-durations.js.map +1 -0
- package/dist/bundle-storyboard.d.ts +11 -0
- package/dist/bundle-storyboard.d.ts.map +1 -0
- package/dist/bundle-storyboard.js +82 -0
- package/dist/bundle-storyboard.js.map +1 -0
- package/dist/captions.d.ts +25 -0
- package/dist/captions.d.ts.map +1 -0
- package/dist/captions.js +56 -0
- package/dist/captions.js.map +1 -0
- package/dist/cast-db.d.ts +61 -0
- package/dist/cast-db.d.ts.map +1 -0
- package/dist/cast-db.js +324 -0
- package/dist/cast-db.js.map +1 -0
- package/dist/cast-lora-train.d.ts +18 -0
- package/dist/cast-lora-train.d.ts.map +1 -0
- package/dist/cast-lora-train.js +215 -0
- package/dist/cast-lora-train.js.map +1 -0
- package/dist/cast-loras.d.ts +21 -0
- package/dist/cast-loras.d.ts.map +1 -0
- package/dist/cast-loras.js +69 -0
- package/dist/cast-loras.js.map +1 -0
- package/dist/clip-content-validate.d.ts +43 -0
- package/dist/clip-content-validate.d.ts.map +1 -0
- package/dist/clip-content-validate.js +115 -0
- package/dist/clip-content-validate.js.map +1 -0
- package/dist/clip-job-model.d.ts +42 -0
- package/dist/clip-job-model.d.ts.map +1 -0
- package/dist/clip-job-model.js +8 -0
- package/dist/clip-job-model.js.map +1 -0
- package/dist/clip-validate.d.ts +33 -0
- package/dist/clip-validate.d.ts.map +1 -0
- package/dist/clip-validate.js +247 -0
- package/dist/clip-validate.js.map +1 -0
- package/dist/d1-retry.d.ts +20 -0
- package/dist/d1-retry.d.ts.map +1 -0
- package/dist/d1-retry.js +67 -0
- package/dist/d1-retry.js.map +1 -0
- package/dist/db-env.d.ts +6 -0
- package/dist/db-env.d.ts.map +1 -0
- package/dist/db-env.js +2 -0
- package/dist/db-env.js.map +1 -0
- package/dist/dialogue-lines.d.ts +23 -0
- package/dist/dialogue-lines.d.ts.map +1 -0
- package/dist/dialogue-lines.js +84 -0
- package/dist/dialogue-lines.js.map +1 -0
- package/dist/film-advance-lease.d.ts +9 -0
- package/dist/film-advance-lease.d.ts.map +1 -0
- package/dist/film-advance-lease.js +19 -0
- package/dist/film-advance-lease.js.map +1 -0
- package/dist/film-model.d.ts +415 -0
- package/dist/film-model.d.ts.map +1 -0
- package/dist/film-model.js +541 -0
- package/dist/film-model.js.map +1 -0
- package/dist/film-orchestrator.d.ts +216 -0
- package/dist/film-orchestrator.d.ts.map +1 -0
- package/dist/film-orchestrator.js +2204 -0
- package/dist/film-orchestrator.js.map +1 -0
- package/dist/film-render-bridge.d.ts +26 -0
- package/dist/film-render-bridge.d.ts.map +1 -0
- package/dist/film-render-bridge.js +176 -0
- package/dist/film-render-bridge.js.map +1 -0
- package/dist/finish-hash.d.ts +10 -0
- package/dist/finish-hash.d.ts.map +1 -0
- package/dist/finish-hash.js +60 -0
- package/dist/finish-hash.js.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -0
- package/dist/key-safety.d.ts +5 -0
- package/dist/key-safety.d.ts.map +1 -0
- package/dist/key-safety.js +23 -0
- package/dist/key-safety.js.map +1 -0
- package/dist/lora-bundle.d.ts +19 -0
- package/dist/lora-bundle.d.ts.map +1 -0
- package/dist/lora-bundle.js +75 -0
- package/dist/lora-bundle.js.map +1 -0
- package/dist/lora-keys.d.ts +2 -0
- package/dist/lora-keys.d.ts.map +1 -0
- package/dist/lora-keys.js +4 -0
- package/dist/lora-keys.js.map +1 -0
- package/dist/modules/conformance.d.ts +58 -0
- package/dist/modules/conformance.d.ts.map +1 -0
- package/dist/modules/conformance.js +369 -0
- package/dist/modules/conformance.js.map +1 -0
- package/dist/modules/manifest-validate.d.ts +5 -0
- package/dist/modules/manifest-validate.d.ts.map +1 -0
- package/dist/modules/manifest-validate.js +60 -0
- package/dist/modules/manifest-validate.js.map +1 -0
- package/dist/modules/registry.d.ts +184 -0
- package/dist/modules/registry.d.ts.map +1 -0
- package/dist/modules/registry.js +653 -0
- package/dist/modules/registry.js.map +1 -0
- package/dist/modules/render-pipeline.d.ts +38 -0
- package/dist/modules/render-pipeline.d.ts.map +1 -0
- package/dist/modules/render-pipeline.js +40 -0
- package/dist/modules/render-pipeline.js.map +1 -0
- package/dist/modules/types.d.ts +544 -0
- package/dist/modules/types.d.ts.map +1 -0
- package/dist/modules/types.js +57 -0
- package/dist/modules/types.js.map +1 -0
- package/dist/operator-config.d.ts +20 -0
- package/dist/operator-config.d.ts.map +1 -0
- package/dist/operator-config.js +85 -0
- package/dist/operator-config.js.map +1 -0
- package/dist/output-extract.d.ts +8 -0
- package/dist/output-extract.d.ts.map +1 -0
- package/dist/output-extract.js +129 -0
- package/dist/output-extract.js.map +1 -0
- package/dist/planner-prompt.d.ts +12 -0
- package/dist/planner-prompt.d.ts.map +1 -0
- package/dist/planner-prompt.js +337 -0
- package/dist/planner-prompt.js.map +1 -0
- package/dist/planner-yaml.d.ts +15 -0
- package/dist/planner-yaml.d.ts.map +1 -0
- package/dist/planner-yaml.js +157 -0
- package/dist/planner-yaml.js.map +1 -0
- package/dist/platform/fetcher.d.ts +3 -0
- package/dist/platform/fetcher.d.ts.map +1 -0
- package/dist/platform/fetcher.js +7 -0
- package/dist/platform/fetcher.js.map +1 -0
- package/dist/platform/index.d.ts +6 -0
- package/dist/platform/index.d.ts.map +1 -0
- package/dist/platform/index.js +5 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/object-store-r2.d.ts +26 -0
- package/dist/platform/object-store-r2.d.ts.map +1 -0
- package/dist/platform/object-store-r2.js +74 -0
- package/dist/platform/object-store-r2.js.map +1 -0
- package/dist/platform/orchestrator-context.d.ts +20 -0
- package/dist/platform/orchestrator-context.d.ts.map +1 -0
- package/dist/platform/orchestrator-context.js +28 -0
- package/dist/platform/orchestrator-context.js.map +1 -0
- package/dist/platform/r2-types.d.ts +41 -0
- package/dist/platform/r2-types.d.ts.map +1 -0
- package/dist/platform/r2-types.js +3 -0
- package/dist/platform/r2-types.js.map +1 -0
- package/dist/platform/types.d.ts +90 -0
- package/dist/platform/types.d.ts.map +1 -0
- package/dist/platform/types.js +23 -0
- package/dist/platform/types.js.map +1 -0
- package/dist/preflight.d.ts +57 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +243 -0
- package/dist/preflight.js.map +1 -0
- package/dist/presign.d.ts +5 -0
- package/dist/presign.d.ts.map +1 -0
- package/dist/presign.js +24 -0
- package/dist/presign.js.map +1 -0
- package/dist/public-id.d.ts +8 -0
- package/dist/public-id.d.ts.map +1 -0
- package/dist/public-id.js +25 -0
- package/dist/public-id.js.map +1 -0
- package/dist/render-adopt.d.ts +3 -0
- package/dist/render-adopt.d.ts.map +1 -0
- package/dist/render-adopt.js +92 -0
- package/dist/render-adopt.js.map +1 -0
- package/dist/render-log.d.ts +25 -0
- package/dist/render-log.d.ts.map +1 -0
- package/dist/render-log.js +139 -0
- package/dist/render-log.js.map +1 -0
- package/dist/render-module-config.d.ts +28 -0
- package/dist/render-module-config.d.ts.map +1 -0
- package/dist/render-module-config.js +124 -0
- package/dist/render-module-config.js.map +1 -0
- package/dist/render-mux.d.ts +18 -0
- package/dist/render-mux.d.ts.map +1 -0
- package/dist/render-mux.js +69 -0
- package/dist/render-mux.js.map +1 -0
- package/dist/render-orchestrator.d.ts +71 -0
- package/dist/render-orchestrator.d.ts.map +1 -0
- package/dist/render-orchestrator.js +376 -0
- package/dist/render-orchestrator.js.map +1 -0
- package/dist/render-sweep.d.ts +5 -0
- package/dist/render-sweep.d.ts.map +1 -0
- package/dist/render-sweep.js +62 -0
- package/dist/render-sweep.js.map +1 -0
- package/dist/renders-db.d.ts +129 -0
- package/dist/renders-db.d.ts.map +1 -0
- package/dist/renders-db.js +752 -0
- package/dist/renders-db.js.map +1 -0
- package/dist/runpod-submit.d.ts +151 -0
- package/dist/runpod-submit.d.ts.map +1 -0
- package/dist/runpod-submit.js +469 -0
- package/dist/runpod-submit.js.map +1 -0
- package/dist/runpod-types.d.ts +2 -0
- package/dist/runpod-types.d.ts.map +1 -0
- package/dist/runpod-types.js +4 -0
- package/dist/runpod-types.js.map +1 -0
- package/dist/scatter-notify.d.ts +4 -0
- package/dist/scatter-notify.d.ts.map +1 -0
- package/dist/scatter-notify.js +43 -0
- package/dist/scatter-notify.js.map +1 -0
- package/dist/scatter-orchestrator-types.d.ts +56 -0
- package/dist/scatter-orchestrator-types.d.ts.map +1 -0
- package/dist/scatter-orchestrator-types.js +3 -0
- package/dist/scatter-orchestrator-types.js.map +1 -0
- package/dist/scatter-orchestrator.d.ts +70 -0
- package/dist/scatter-orchestrator.d.ts.map +1 -0
- package/dist/scatter-orchestrator.js +614 -0
- package/dist/scatter-orchestrator.js.map +1 -0
- package/dist/scatter.d.ts +35 -0
- package/dist/scatter.d.ts.map +1 -0
- package/dist/scatter.js +171 -0
- package/dist/scatter.js.map +1 -0
- package/dist/secret-store.d.ts +5 -0
- package/dist/secret-store.d.ts.map +1 -0
- package/dist/secret-store.js +19 -0
- package/dist/secret-store.js.map +1 -0
- package/dist/shot-durations-parse.d.ts +3 -0
- package/dist/shot-durations-parse.d.ts.map +1 -0
- package/dist/shot-durations-parse.js +43 -0
- package/dist/shot-durations-parse.js.map +1 -0
- package/dist/srt.d.ts +11 -0
- package/dist/srt.d.ts.map +1 -0
- package/dist/srt.js +54 -0
- package/dist/srt.js.map +1 -0
- package/dist/storyboard-ids.d.ts +2 -0
- package/dist/storyboard-ids.d.ts.map +1 -0
- package/dist/storyboard-ids.js +12 -0
- package/dist/storyboard-ids.js.map +1 -0
- package/dist/storyboard-projects-db.d.ts +31 -0
- package/dist/storyboard-projects-db.d.ts.map +1 -0
- package/dist/storyboard-projects-db.js +153 -0
- package/dist/storyboard-projects-db.js.map +1 -0
- package/dist/storyboard-validate.d.ts +72 -0
- package/dist/storyboard-validate.d.ts.map +1 -0
- package/dist/storyboard-validate.js +595 -0
- package/dist/storyboard-validate.js.map +1 -0
- package/dist/structured-events.d.ts +6 -0
- package/dist/structured-events.d.ts.map +1 -0
- package/dist/structured-events.js +15 -0
- package/dist/structured-events.js.map +1 -0
- package/dist/tar.d.ts +9 -0
- package/dist/tar.d.ts.map +1 -0
- package/dist/tar.js +151 -0
- package/dist/tar.js.map +1 -0
- package/dist/voices.d.ts +12 -0
- package/dist/voices.d.ts.map +1 -0
- package/dist/voices.js +37 -0
- package/dist/voices.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// #523 Layer 1: in-Worker STRUCTURAL clip validation at motion-clip intake.
|
|
2
|
+
//
|
|
3
|
+
// A CF Worker has no video decoder, so it CANNOT inspect pixels. What it CAN do, cheaply, is parse
|
|
4
|
+
// the mp4 box tree from a few ranged reads and reject the STRUCTURAL-corruption class -- truncated /
|
|
5
|
+
// empty uploads, zero-frame or zero-duration containers, non-mp4 bodies -- BEFORE the finish /
|
|
6
|
+
// dialogue / upscale chain spends anything downstream (#523: a satellite GPU upscaled 411KB of noise
|
|
7
|
+
// to 2.8MB because nothing looked at the clip).
|
|
8
|
+
//
|
|
9
|
+
// HONEST LIMIT (stated so no one mistakes this for full coverage): the local-16gb#35 pure-noise clip
|
|
10
|
+
// is a STRUCTURALLY VALID mp4 (right duration, right dimensions, real frame count). Byte entropy of an
|
|
11
|
+
// h264 bitstream is high for both noise and real content, so it is not separable in-Worker. Detecting
|
|
12
|
+
// noise needs a pixel decode; that is Layer 2, a pre-finish gate in the video-finish CPU container
|
|
13
|
+
// (which already runs ffmpeg), filed as a follow-up and wired at this same clip-intake seam.
|
|
14
|
+
//
|
|
15
|
+
// The parser is split into a PURE core (parseMoov / judgeClip over an in-memory buffer, unit-tested
|
|
16
|
+
// with synthetic fixtures) and an async wrapper (validateClipArtifact) that does the bounded R2 reads.
|
|
17
|
+
// --- Tunable bounds (core render behavior, not a per-module knob; see CONTRACT + observability docs).
|
|
18
|
+
// Deliberately LENIENT: these are set so a real clip from ANY motion.backend (a fixed-length CogVideoX
|
|
19
|
+
// 49-frame clip, an 8fps LTX clip, a cloud i2v clip) cannot trip them. They catch only degenerate
|
|
20
|
+
// output, never legitimate content -- the false-positive floor #523 asked for.
|
|
21
|
+
export const CLIP_VALIDATE_ENABLED = true;
|
|
22
|
+
export const CLIP_MIN_BYTES = 2048; // a real mp4 (even 1s) is far larger; catches truncated/empty
|
|
23
|
+
export const CLIP_MIN_DURATION_S = 0.15; // catches a zero / near-zero duration container
|
|
24
|
+
export const CLIP_MAX_DURATION_S = 900; // runaway guard only (15 min); never a real per-shot clip
|
|
25
|
+
export const CLIP_MAX_DIMENSION = 16384; // sane upper bound on a track dimension
|
|
26
|
+
// Cap the moov bytes we pull into memory. moov is metadata (sample tables), small vs the mdat; a few
|
|
27
|
+
// hundred KB even for long videos. If it is larger than this we treat the container as present but
|
|
28
|
+
// skip the deep (frame/dimension) checks rather than pull an unbounded object into the isolate.
|
|
29
|
+
export const MOOV_FETCH_CAP = 8 * 1024 * 1024;
|
|
30
|
+
// Cap on how far we scan top-level box headers to locate moov (a handful of boxes; guards a hostile file).
|
|
31
|
+
const MAX_TOPLEVEL_BOXES = 64;
|
|
32
|
+
// Big-endian readers over a Uint8Array.
|
|
33
|
+
function u32(b, o) {
|
|
34
|
+
return (b[o] * 0x1000000) + (b[o + 1] << 16) + (b[o + 2] << 8) + b[o + 3];
|
|
35
|
+
}
|
|
36
|
+
function u64(b, o) {
|
|
37
|
+
// File offsets / durations stay well under 2^53, so a Number is exact here.
|
|
38
|
+
return u32(b, o) * 0x100000000 + u32(b, o + 4);
|
|
39
|
+
}
|
|
40
|
+
function boxType(b, o) {
|
|
41
|
+
return String.fromCharCode(b[o], b[o + 1], b[o + 2], b[o + 3]);
|
|
42
|
+
}
|
|
43
|
+
// Parse one box header at offset o within b. Returns null if there are not enough bytes.
|
|
44
|
+
function readBoxHeader(b, o) {
|
|
45
|
+
if (o + 8 > b.length)
|
|
46
|
+
return null;
|
|
47
|
+
let size = u32(b, o);
|
|
48
|
+
const type = boxType(b, o + 4);
|
|
49
|
+
let headerSize = 8;
|
|
50
|
+
if (size === 1) {
|
|
51
|
+
if (o + 16 > b.length)
|
|
52
|
+
return null;
|
|
53
|
+
size = u64(b, o + 8);
|
|
54
|
+
headerSize = 16;
|
|
55
|
+
}
|
|
56
|
+
return { type, size, headerSize };
|
|
57
|
+
}
|
|
58
|
+
const CONTAINER_BOXES = new Set(["moov", "trak", "mdia", "minf", "stbl", "edts", "mvex"]);
|
|
59
|
+
// PURE: parse a moov box PAYLOAD region [start,end) for the handful of facts the structural gate needs.
|
|
60
|
+
// Descends the standard container nesting (moov > trak > mdia > minf > stbl) and reads mvhd (duration),
|
|
61
|
+
// tkhd (dimensions), hdlr (is this track video?) and stsz (frame count). Robust to unknown/extra boxes
|
|
62
|
+
// and to a truncated buffer -- it reads what it can and reports nulls for the rest, never throwing.
|
|
63
|
+
export function parseMoov(buf, start = 0, end = buf.length) {
|
|
64
|
+
const info = { durationS: null, hasVideoTrack: false, frames: null, width: null, height: null };
|
|
65
|
+
walk(buf, start, end, info, { inVideoTrak: false });
|
|
66
|
+
return info;
|
|
67
|
+
}
|
|
68
|
+
function walk(buf, start, end, info, ctx) {
|
|
69
|
+
let o = start;
|
|
70
|
+
while (o + 8 <= end) {
|
|
71
|
+
const h = readBoxHeader(buf, o);
|
|
72
|
+
if (!h)
|
|
73
|
+
break;
|
|
74
|
+
const boxEnd = h.size === 0 ? end : o + h.size;
|
|
75
|
+
if (h.size !== 0 && (h.size < h.headerSize || boxEnd > end))
|
|
76
|
+
break; // malformed / overruns: stop honestly
|
|
77
|
+
const payloadStart = o + h.headerSize;
|
|
78
|
+
if (h.type === "trak") {
|
|
79
|
+
const tctx = { inVideoTrak: false };
|
|
80
|
+
walk(buf, payloadStart, boxEnd, info, tctx);
|
|
81
|
+
if (tctx.inVideoTrak) {
|
|
82
|
+
info.hasVideoTrack = true;
|
|
83
|
+
if (tctx.trakWidth != null && info.width == null)
|
|
84
|
+
info.width = tctx.trakWidth;
|
|
85
|
+
if (tctx.trakHeight != null && info.height == null)
|
|
86
|
+
info.height = tctx.trakHeight;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (CONTAINER_BOXES.has(h.type)) {
|
|
90
|
+
walk(buf, payloadStart, boxEnd, info, ctx);
|
|
91
|
+
}
|
|
92
|
+
else if (h.type === "mvhd") {
|
|
93
|
+
readMvhd(buf, payloadStart, boxEnd, info);
|
|
94
|
+
}
|
|
95
|
+
else if (h.type === "tkhd") {
|
|
96
|
+
readTkhd(buf, payloadStart, boxEnd, ctx);
|
|
97
|
+
}
|
|
98
|
+
else if (h.type === "hdlr") {
|
|
99
|
+
// handler_type is at payload offset 8 (after version/flags + pre_defined).
|
|
100
|
+
if (payloadStart + 12 <= boxEnd && boxType(buf, payloadStart + 8) === "vide")
|
|
101
|
+
ctx.inVideoTrak = true;
|
|
102
|
+
}
|
|
103
|
+
else if (h.type === "stsz") {
|
|
104
|
+
// version/flags(4) sample_size(4) sample_count(4)
|
|
105
|
+
if (payloadStart + 12 <= boxEnd && ctx.inVideoTrak) {
|
|
106
|
+
const count = u32(buf, payloadStart + 8);
|
|
107
|
+
info.frames = (info.frames ?? 0) + count;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (h.size === 0)
|
|
111
|
+
break; // extends to end
|
|
112
|
+
o = boxEnd;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function readMvhd(buf, payloadStart, end, info) {
|
|
116
|
+
if (payloadStart + 4 > end)
|
|
117
|
+
return;
|
|
118
|
+
const version = buf[payloadStart];
|
|
119
|
+
let ts, dur;
|
|
120
|
+
if (version === 1) {
|
|
121
|
+
if (payloadStart + 28 > end)
|
|
122
|
+
return;
|
|
123
|
+
ts = u32(buf, payloadStart + 20);
|
|
124
|
+
dur = u64(buf, payloadStart + 24);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
if (payloadStart + 20 > end)
|
|
128
|
+
return;
|
|
129
|
+
ts = u32(buf, payloadStart + 12);
|
|
130
|
+
dur = u32(buf, payloadStart + 16);
|
|
131
|
+
}
|
|
132
|
+
if (ts > 0)
|
|
133
|
+
info.durationS = dur / ts;
|
|
134
|
+
}
|
|
135
|
+
function readTkhd(buf, payloadStart, end, ctx) {
|
|
136
|
+
if (payloadStart + 4 > end)
|
|
137
|
+
return;
|
|
138
|
+
const version = buf[payloadStart];
|
|
139
|
+
// width/height are the last two 32-bit 16.16 fixed-point fields of tkhd. `base` is the payload offset
|
|
140
|
+
// JUST AFTER the duration field: v0 = ver/flags(4) creation(4) modification(4) track_ID(4) reserved(4)
|
|
141
|
+
// duration(4) = 24; v1 = ver/flags(4) creation(8) modification(8) track_ID(4) reserved(4) duration(8) =
|
|
142
|
+
// 36 (ISO 14496-12; the reserved(4) between track_ID and duration is easy to drop -- do not).
|
|
143
|
+
const base = version === 1 ? payloadStart + 36 : payloadStart + 24;
|
|
144
|
+
const wOff = base + 8 + 2 + 2 + 2 + 2 + 36; // reserved(8) layer(2) alt(2) vol(2) reserved(2) matrix(36)
|
|
145
|
+
if (wOff + 8 > end)
|
|
146
|
+
return;
|
|
147
|
+
ctx.trakWidth = u32(buf, wOff) >>> 16;
|
|
148
|
+
ctx.trakHeight = u32(buf, wOff + 4) >>> 16;
|
|
149
|
+
}
|
|
150
|
+
// PURE: turn the collected checks into a verdict. Fails ONLY on positive evidence of structural
|
|
151
|
+
// corruption (never on a missing read -- that is a skip). Kept separate from I/O so it is exhaustively
|
|
152
|
+
// unit-testable.
|
|
153
|
+
export function judgeClip(checks) {
|
|
154
|
+
if (checks.bytes < CLIP_MIN_BYTES) {
|
|
155
|
+
return { verdict: "fail", reason: `clip is ${checks.bytes} bytes (< ${CLIP_MIN_BYTES} floor); truncated or empty`, checks };
|
|
156
|
+
}
|
|
157
|
+
if (!checks.container) {
|
|
158
|
+
return { verdict: "fail", reason: "not a valid mp4 (no ftyp/moov box tree); corrupt or wrong format", checks };
|
|
159
|
+
}
|
|
160
|
+
if (checks.duration_s != null && (checks.duration_s < CLIP_MIN_DURATION_S || checks.duration_s > CLIP_MAX_DURATION_S)) {
|
|
161
|
+
return { verdict: "fail", reason: `clip duration ${checks.duration_s.toFixed(3)}s is out of sane bounds [${CLIP_MIN_DURATION_S}, ${CLIP_MAX_DURATION_S}]s`, checks };
|
|
162
|
+
}
|
|
163
|
+
if (!checks.video_track) {
|
|
164
|
+
return { verdict: "fail", reason: "no video track in the clip (audio-only or corrupt container)", checks };
|
|
165
|
+
}
|
|
166
|
+
if (checks.frames != null && checks.frames <= 0) {
|
|
167
|
+
return { verdict: "fail", reason: "video track has zero frames (empty/corrupt clip)", checks };
|
|
168
|
+
}
|
|
169
|
+
if (checks.width != null && checks.height != null &&
|
|
170
|
+
(checks.width <= 0 || checks.height <= 0 || checks.width > CLIP_MAX_DIMENSION || checks.height > CLIP_MAX_DIMENSION)) {
|
|
171
|
+
return { verdict: "fail", reason: `video dimensions ${checks.width}x${checks.height} are out of sane bounds`, checks };
|
|
172
|
+
}
|
|
173
|
+
return { verdict: "pass", checks };
|
|
174
|
+
}
|
|
175
|
+
// Locate ftyp + moov by walking top-level box HEADERS only (each read is a few bytes; mdat is skipped by
|
|
176
|
+
// its size, never downloaded). moov may be at the front (faststart) or after mdat; both are handled.
|
|
177
|
+
async function locateStructure(read, totalBytes) {
|
|
178
|
+
let ftypOk = false;
|
|
179
|
+
let offset = 0;
|
|
180
|
+
for (let i = 0; i < MAX_TOPLEVEL_BOXES && offset + 8 <= totalBytes; i++) {
|
|
181
|
+
const hdrBytes = await read(offset, 16);
|
|
182
|
+
if (!hdrBytes || hdrBytes.length < 8)
|
|
183
|
+
break;
|
|
184
|
+
const h = readBoxHeader(hdrBytes, 0);
|
|
185
|
+
if (!h)
|
|
186
|
+
break;
|
|
187
|
+
if (i === 0) {
|
|
188
|
+
if (h.type !== "ftyp")
|
|
189
|
+
break; // a real mp4 opens with ftyp; anything else is not our format
|
|
190
|
+
ftypOk = true;
|
|
191
|
+
}
|
|
192
|
+
if (h.type === "moov")
|
|
193
|
+
return { ftypOk, moov: { offset, size: h.size, headerSize: h.headerSize } };
|
|
194
|
+
if (h.size === 0)
|
|
195
|
+
break; // last box extends to EOF and is not moov
|
|
196
|
+
offset += h.size;
|
|
197
|
+
}
|
|
198
|
+
return { ftypOk };
|
|
199
|
+
}
|
|
200
|
+
// Async wrapper: bounded R2 reads -> structural checks -> verdict. NEVER throws; an unreadable artifact
|
|
201
|
+
// is a "skip" (transient), not a fail, so a blip cannot false-reject a real render. Returns skip when
|
|
202
|
+
// validation is disabled or the object is absent.
|
|
203
|
+
export async function validateClipArtifact(env, key, expectedSeconds) {
|
|
204
|
+
const empty = { container: false, video_track: false, duration_s: null, expected_s: expectedSeconds, frames: null, width: null, height: null, bytes: 0 };
|
|
205
|
+
if (!CLIP_VALIDATE_ENABLED)
|
|
206
|
+
return { verdict: "skip", reason: "clip validation disabled", checks: empty };
|
|
207
|
+
try {
|
|
208
|
+
const head = await env.R2_RENDERS.head(key);
|
|
209
|
+
if (!head)
|
|
210
|
+
return { verdict: "skip", reason: "clip artifact not found in R2", checks: empty };
|
|
211
|
+
const totalBytes = head.size;
|
|
212
|
+
const checks = { ...empty, bytes: totalBytes };
|
|
213
|
+
const read = async (offset, length) => {
|
|
214
|
+
const obj = await env.R2_RENDERS.get(key, { range: { offset, length } });
|
|
215
|
+
if (!obj)
|
|
216
|
+
return null;
|
|
217
|
+
return new Uint8Array(await obj.arrayBuffer());
|
|
218
|
+
};
|
|
219
|
+
const loc = await locateStructure(read, totalBytes);
|
|
220
|
+
if (loc.ftypOk && loc.moov) {
|
|
221
|
+
checks.container = true;
|
|
222
|
+
const payloadOffset = loc.moov.offset + loc.moov.headerSize;
|
|
223
|
+
const payloadLen = loc.moov.size - loc.moov.headerSize;
|
|
224
|
+
if (payloadLen > 0 && payloadLen <= MOOV_FETCH_CAP) {
|
|
225
|
+
const moovBytes = await read(payloadOffset, payloadLen);
|
|
226
|
+
if (moovBytes && moovBytes.length) {
|
|
227
|
+
const info = parseMoov(moovBytes);
|
|
228
|
+
checks.duration_s = info.durationS;
|
|
229
|
+
checks.video_track = info.hasVideoTrack;
|
|
230
|
+
checks.frames = info.frames;
|
|
231
|
+
checks.width = info.width;
|
|
232
|
+
checks.height = info.height;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
// moov present but too large to introspect: trust the container, skip the deep checks.
|
|
237
|
+
checks.video_track = true;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return judgeClip(checks);
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
// Reading/parsing threw: do NOT fail the shot on an I/O hiccup; skip and let the render proceed.
|
|
244
|
+
return { verdict: "skip", reason: `clip validation errored: ${e instanceof Error ? e.message : String(e)}`, checks: empty };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=clip-validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clip-validate.js","sourceRoot":"","sources":["../src/clip-validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare function isTransientD1Error(err: unknown): boolean;
|
|
2
|
+
/** A short, queryable signature for a D1 error -- the first line, truncated. Goes into the
|
|
3
|
+
* structured d1.retry / d1.exhausted / d1.error log lines so a blip is greppable in
|
|
4
|
+
* observability rather than a multi-line stack buried in a console.warn. */
|
|
5
|
+
export declare function d1ErrorCode(err: unknown): string;
|
|
6
|
+
export interface D1RetryOptions {
|
|
7
|
+
/** Total attempts including the first (default 4: initial + 3 retries). */
|
|
8
|
+
attempts?: number;
|
|
9
|
+
/** Base backoff in ms; delay before retry i is base * 2^i (+ jitter): ~50, 100, 200. */
|
|
10
|
+
baseDelayMs?: number;
|
|
11
|
+
/** Injectable sleep so tests don't wait on the wall clock. */
|
|
12
|
+
sleep?: (ms: number) => Promise<void>;
|
|
13
|
+
/** Short op name for the structured d1.retry / d1.exhausted log lines (observability). */
|
|
14
|
+
label?: string;
|
|
15
|
+
}
|
|
16
|
+
/** Run `fn`, retrying ONLY transient D1 errors with short exponential backoff. Re-throws a
|
|
17
|
+
* non-transient error (or the last transient one after exhausting attempts) so the caller's
|
|
18
|
+
* existing failure handling still runs. */
|
|
19
|
+
export declare function withD1Retry<T>(fn: () => Promise<T>, opts?: D1RetryOptions): Promise<T>;
|
|
20
|
+
//# sourceMappingURL=d1-retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"d1-retry.d.ts","sourceRoot":"","sources":["../src/d1-retry.ts"],"names":[],"mappings":"AA0BA,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAKxD;AAED;;6EAE6E;AAC7E,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAGhD;AAED,MAAM,WAAW,cAAc;IAC7B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wFAAwF;IACxF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,0FAA0F;IAC1F,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID;;4CAE4C;AAC5C,wBAAsB,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,CAAC,CAAC,CAyBhG"}
|
package/dist/d1-retry.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Transient-D1 retry for the render-advance hot path.
|
|
2
|
+
//
|
|
3
|
+
// The every-60s cron sweep (sweepUnresolvedJobs -> advanceScatterJob / advanceFilmJob ->
|
|
4
|
+
// updateRenderFromView) IS the self-heal for client-less renders. But a transient
|
|
5
|
+
// `D1_ERROR: internal error` (a Cloudflare D1 platform blip -- intermittent, clears in seconds)
|
|
6
|
+
// thrown by any D1 op aborts the whole advance: render-sweep catches it, warns, and skips, so the
|
|
7
|
+
// shard never progresses and every 60s retry re-fails the same way through the flaky window. A
|
|
8
|
+
// proven failure mode: two full scatter renders wedged with one shard stuck polling RIFE and the
|
|
9
|
+
// other at keyframe, GPU idle, until the ~30-min dead-shard watchdog failed them.
|
|
10
|
+
//
|
|
11
|
+
// withD1Retry transparently retries ONLY transient D1 errors a few times with short backoff, so a
|
|
12
|
+
// blip self-heals inside the tick. Constraint / SQL / logic errors are re-thrown immediately --
|
|
13
|
+
// those are real and must fail fast, never spin. The wrapped ops are all keyed on job_id/id and
|
|
14
|
+
// write absolute values (terminal-guarded), so they are idempotent under retry.
|
|
15
|
+
import { emitStructuredEvent } from "./structured-events.js";
|
|
16
|
+
/** Errors that are real and must NOT be retried (a constraint violation, bad SQL, schema mismatch).
|
|
17
|
+
* Checked first so a `D1_ERROR: UNIQUE constraint failed` fails fast even though it says D1_ERROR. */
|
|
18
|
+
const D1_FATAL = /(constraint failed|no such (table|column|index)|syntax error|datatype mismatch|not null|unique|readonly|malformed|ambiguous column|too many|foreign key)/i;
|
|
19
|
+
/** Transient signatures worth retrying: D1 platform blips and dropped connections. */
|
|
20
|
+
const D1_TRANSIENT = /(d1_error[\s\S]*?(internal error|transient|temporar|overloaded|try again|reset|timed? ?out|unavailable))|(network connection lost)|(storage (caused|operation)[\s\S]*?(reset|lost|error))|(\b50[02-4]\b)/i;
|
|
21
|
+
export function isTransientD1Error(err) {
|
|
22
|
+
const msg = err instanceof Error ? err.message : String(err ?? "");
|
|
23
|
+
if (!msg)
|
|
24
|
+
return false;
|
|
25
|
+
if (D1_FATAL.test(msg))
|
|
26
|
+
return false; // a real error -- fail fast
|
|
27
|
+
return D1_TRANSIENT.test(msg);
|
|
28
|
+
}
|
|
29
|
+
/** A short, queryable signature for a D1 error -- the first line, truncated. Goes into the
|
|
30
|
+
* structured d1.retry / d1.exhausted / d1.error log lines so a blip is greppable in
|
|
31
|
+
* observability rather than a multi-line stack buried in a console.warn. */
|
|
32
|
+
export function d1ErrorCode(err) {
|
|
33
|
+
const msg = err instanceof Error ? err.message : String(err ?? "");
|
|
34
|
+
return (msg.split("\n")[0] || "unknown").slice(0, 120);
|
|
35
|
+
}
|
|
36
|
+
const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
37
|
+
/** Run `fn`, retrying ONLY transient D1 errors with short exponential backoff. Re-throws a
|
|
38
|
+
* non-transient error (or the last transient one after exhausting attempts) so the caller's
|
|
39
|
+
* existing failure handling still runs. */
|
|
40
|
+
export async function withD1Retry(fn, opts = {}) {
|
|
41
|
+
const attempts = Math.max(1, opts.attempts ?? 4);
|
|
42
|
+
const base = Math.max(1, opts.baseDelayMs ?? 50);
|
|
43
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
44
|
+
let lastErr;
|
|
45
|
+
for (let i = 0; i < attempts; i++) {
|
|
46
|
+
try {
|
|
47
|
+
return await fn();
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
lastErr = err;
|
|
51
|
+
const transient = isTransientD1Error(err);
|
|
52
|
+
if (i === attempts - 1 || !transient) {
|
|
53
|
+
// A non-transient error fails fast; an EXHAUSTED transient blip is logged so a recurring
|
|
54
|
+
// D1 flaky window shows up as a queryable d1.exhausted count, not a silent throw.
|
|
55
|
+
if (transient) {
|
|
56
|
+
emitStructuredEvent({ ev: "d1.exhausted", op: opts.label, attempts: i + 1, code: d1ErrorCode(err) });
|
|
57
|
+
}
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
emitStructuredEvent({ ev: "d1.retry", op: opts.label, attempt: i + 1, code: d1ErrorCode(err) });
|
|
61
|
+
const jitter = Math.floor(Math.random() * base);
|
|
62
|
+
await sleep(base * 2 ** i + jitter);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
throw lastErr;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=d1-retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"d1-retry.js","sourceRoot":"","sources":["../src/d1-retry.ts"],"names":[],"mappings":"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"}
|
package/dist/db-env.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-env.d.ts","sourceRoot":"","sources":["../src/db-env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,iEAAiE;AACjE,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,QAAQ,CAAC;CACd"}
|
package/dist/db-env.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-env.js","sourceRoot":"","sources":["../src/db-env.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DialogueLine } from "./modules/types.js";
|
|
2
|
+
import type { ParsedBundleScene } from "./planner-yaml.js";
|
|
3
|
+
/** Per-shot dialogue lines for the shots being rendered. `voices` is slot -> voice_id (from
|
|
4
|
+
* resolveCastLoras); `shotIds` is the render's shot set (so dialogue for shots not in this render --
|
|
5
|
+
* e.g. a scatter shard -- is excluded). A scene with no/invalid dialogue is skipped (silent shot).
|
|
6
|
+
* Pure + defensive: the stored storyboard is untyped JSON, so every field is checked. */
|
|
7
|
+
export declare function buildDialogueLines(storyboard: unknown, voices: Record<string, string>, shotIds: string[]): DialogueLine[];
|
|
8
|
+
/** Cast voicing for EXPLICIT dialogue_lines (#582): a caller-supplied line without a voice_id used
|
|
9
|
+
* to fall straight to DEFAULT_VOICE_ID -- even when the shot's speaking slot is bound to a cast
|
|
10
|
+
* member with a voice (Wren, voice asteria, spoke as angus in film-08dd5777). Resolve each
|
|
11
|
+
* voiceless line's shot to its speaking slot via the bundle storyboard's per-shot dialogue, then
|
|
12
|
+
* the slot to its cast voice via `voices` (slot -> voice_id, from resolveCastLoras). The default
|
|
13
|
+
* applies ONLY when there is genuinely no mapping (no scene dialogue for the shot, or a slot whose
|
|
14
|
+
* cast has no voice). An explicit line voice_id always wins -- it is never overwritten. Pure. */
|
|
15
|
+
export declare function resolveExplicitLineVoices(lines: DialogueLine[], scenes: ParsedBundleScene[], voices: Record<string, string>): DialogueLine[];
|
|
16
|
+
/** Bundle-only voicing (#313): build the per-shot dialogue batch from the dialogue carried in a
|
|
17
|
+
* bundle's storyboard.yaml (parsed by parseStoryboardScenes), resolving each speaking slot's voice
|
|
18
|
+
* from `voices` (slot -> voice_id; empty on a bundle-only render with no cast) and defaulting
|
|
19
|
+
* otherwise. A scene with no/blank dialogue is skipped (silent shot). The /api/render/film path uses
|
|
20
|
+
* this when the caller passed no explicit dialogue_lines, so a self-describing dialogue bundle renders
|
|
21
|
+
* voiced end to end with no D1 project or arg. */
|
|
22
|
+
export declare function dialogueLinesFromBundleScenes(scenes: ParsedBundleScene[], voices: Record<string, string>): DialogueLine[];
|
|
23
|
+
//# sourceMappingURL=dialogue-lines.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialogue-lines.d.ts","sourceRoot":"","sources":["../src/dialogue-lines.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAc3D;;;0FAG0F;AAC1F,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,OAAO,EACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,OAAO,EAAE,MAAM,EAAE,GAChB,YAAY,EAAE,CAmBhB;AAGD;;;;;;kGAMkG;AAClG,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,YAAY,EAAE,EACrB,MAAM,EAAE,iBAAiB,EAAE,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,YAAY,EAAE,CAWhB;AAED;;;;;mDAKmD;AACnD,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,iBAAiB,EAAE,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,YAAY,EAAE,CAShB"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Build the per-shot dialogue batch for the `dialogue` hook from the AUTHORITATIVE storyboard.
|
|
2
|
+
//
|
|
3
|
+
// The storyboard is persisted lossless in D1 (storyboard_projects.last_storyboard); the bundle's
|
|
4
|
+
// storyboard.yaml is a lossy GPU-render snapshot that drops dialogue. So at render submission we read
|
|
5
|
+
// the stored storyboard, pull each speaking shot's line, and resolve the speaker's voice from the
|
|
6
|
+
// slot -> voice_id map already produced by resolveCastLoras (one cast-row fetch, single-user, no new
|
|
7
|
+
// identity dependency). The line is authored content (from the storyboard); the voice is authoritative
|
|
8
|
+
// (from the cast). Result is threaded onto the FilmJob; the orchestrator submits it after clips.
|
|
9
|
+
import { coerceShotId } from "./storyboard-ids.js";
|
|
10
|
+
import { coerceVoiceId, DEFAULT_VOICE_ID } from "./voices.js";
|
|
11
|
+
/** Defensively pull the scenes[] out of an untyped stored storyboard. */
|
|
12
|
+
function extractScenes(storyboard) {
|
|
13
|
+
if (!storyboard || typeof storyboard !== "object")
|
|
14
|
+
return [];
|
|
15
|
+
const scenes = storyboard.scenes;
|
|
16
|
+
return Array.isArray(scenes) ? scenes : [];
|
|
17
|
+
}
|
|
18
|
+
/** Per-shot dialogue lines for the shots being rendered. `voices` is slot -> voice_id (from
|
|
19
|
+
* resolveCastLoras); `shotIds` is the render's shot set (so dialogue for shots not in this render --
|
|
20
|
+
* e.g. a scatter shard -- is excluded). A scene with no/invalid dialogue is skipped (silent shot).
|
|
21
|
+
* Pure + defensive: the stored storyboard is untyped JSON, so every field is checked. */
|
|
22
|
+
export function buildDialogueLines(storyboard, voices, shotIds) {
|
|
23
|
+
const scenes = extractScenes(storyboard);
|
|
24
|
+
if (!scenes.length)
|
|
25
|
+
return [];
|
|
26
|
+
const want = new Set(shotIds);
|
|
27
|
+
const lines = [];
|
|
28
|
+
scenes.forEach((scene, i) => {
|
|
29
|
+
const dlg = scene.dialogue;
|
|
30
|
+
if (!dlg || typeof dlg !== "object")
|
|
31
|
+
return;
|
|
32
|
+
const slot = dlg.slot;
|
|
33
|
+
const text = dlg.text;
|
|
34
|
+
if (typeof slot !== "string" || typeof text !== "string" || !text.trim())
|
|
35
|
+
return;
|
|
36
|
+
// Reproduce the same shot_NN the validator/bundle assigned, so the line maps to the rendered shot.
|
|
37
|
+
const shotId = coerceShotId(typeof scene.id === "string" ? scene.id : undefined, i);
|
|
38
|
+
if (!want.has(shotId))
|
|
39
|
+
return;
|
|
40
|
+
// Voice from the cast (authoritative); default for a speaker whose cast has none assigned.
|
|
41
|
+
const voice = coerceVoiceId(voices[slot]) ?? DEFAULT_VOICE_ID;
|
|
42
|
+
lines.push({ shot_id: shotId, text: text.trim(), voice_id: voice });
|
|
43
|
+
});
|
|
44
|
+
return lines;
|
|
45
|
+
}
|
|
46
|
+
/** Cast voicing for EXPLICIT dialogue_lines (#582): a caller-supplied line without a voice_id used
|
|
47
|
+
* to fall straight to DEFAULT_VOICE_ID -- even when the shot's speaking slot is bound to a cast
|
|
48
|
+
* member with a voice (Wren, voice asteria, spoke as angus in film-08dd5777). Resolve each
|
|
49
|
+
* voiceless line's shot to its speaking slot via the bundle storyboard's per-shot dialogue, then
|
|
50
|
+
* the slot to its cast voice via `voices` (slot -> voice_id, from resolveCastLoras). The default
|
|
51
|
+
* applies ONLY when there is genuinely no mapping (no scene dialogue for the shot, or a slot whose
|
|
52
|
+
* cast has no voice). An explicit line voice_id always wins -- it is never overwritten. Pure. */
|
|
53
|
+
export function resolveExplicitLineVoices(lines, scenes, voices) {
|
|
54
|
+
const slotByShot = new Map();
|
|
55
|
+
for (const s of scenes) {
|
|
56
|
+
if (s.dialogue && typeof s.dialogue.slot === "string")
|
|
57
|
+
slotByShot.set(s.shot_id, s.dialogue.slot);
|
|
58
|
+
}
|
|
59
|
+
return lines.map((line) => {
|
|
60
|
+
if (typeof line.voice_id === "string" && line.voice_id.trim())
|
|
61
|
+
return line;
|
|
62
|
+
const slot = slotByShot.get(line.shot_id);
|
|
63
|
+
const voice = (slot !== undefined ? coerceVoiceId(voices[slot]) : undefined) ?? DEFAULT_VOICE_ID;
|
|
64
|
+
return { ...line, voice_id: voice };
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/** Bundle-only voicing (#313): build the per-shot dialogue batch from the dialogue carried in a
|
|
68
|
+
* bundle's storyboard.yaml (parsed by parseStoryboardScenes), resolving each speaking slot's voice
|
|
69
|
+
* from `voices` (slot -> voice_id; empty on a bundle-only render with no cast) and defaulting
|
|
70
|
+
* otherwise. A scene with no/blank dialogue is skipped (silent shot). The /api/render/film path uses
|
|
71
|
+
* this when the caller passed no explicit dialogue_lines, so a self-describing dialogue bundle renders
|
|
72
|
+
* voiced end to end with no D1 project or arg. */
|
|
73
|
+
export function dialogueLinesFromBundleScenes(scenes, voices) {
|
|
74
|
+
const lines = [];
|
|
75
|
+
for (const s of scenes) {
|
|
76
|
+
const dlg = s.dialogue;
|
|
77
|
+
if (!dlg || typeof dlg.text !== "string" || !dlg.text.trim())
|
|
78
|
+
continue;
|
|
79
|
+
const voice = coerceVoiceId(voices[dlg.slot]) ?? DEFAULT_VOICE_ID;
|
|
80
|
+
lines.push({ shot_id: s.shot_id, text: dlg.text.trim(), voice_id: voice });
|
|
81
|
+
}
|
|
82
|
+
return lines;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=dialogue-lines.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialogue-lines.js","sourceRoot":"","sources":["../src/dialogue-lines.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Env } from "./platform/orchestrator-context.js";
|
|
2
|
+
export declare const FILM_ADVANCE_LEASE_TTL_SECONDS = 300;
|
|
3
|
+
export interface FilmAdvanceClaim {
|
|
4
|
+
won: boolean;
|
|
5
|
+
lease?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function claimFilmAdvance(env: Env, filmId: string, now?: number): Promise<FilmAdvanceClaim>;
|
|
8
|
+
export declare function releaseFilmAdvance(env: Env, filmId: string, lease: number): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=film-advance-lease.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"film-advance-lease.d.ts","sourceRoot":"","sources":["../src/film-advance-lease.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,oCAAoC,CAAC;AAG9D,eAAO,MAAM,8BAA8B,MAAM,CAAC;AAElD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,MAAM,EACd,GAAG,GAAE,MAAmB,GACvB,OAAO,CAAC,gBAAgB,CAAC,CAe3B;AAED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ/F"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { withD1Retry } from "./d1-retry.js";
|
|
2
|
+
export const FILM_ADVANCE_LEASE_TTL_SECONDS = 300;
|
|
3
|
+
export async function claimFilmAdvance(env, filmId, now = Date.now()) {
|
|
4
|
+
const lease = now + FILM_ADVANCE_LEASE_TTL_SECONDS * 1000;
|
|
5
|
+
const res = await withD1Retry(() => env.DB.prepare(`UPDATE renders SET advance_lease = ?
|
|
6
|
+
WHERE job_id = ? AND (advance_lease IS NULL OR advance_lease < ?)`)
|
|
7
|
+
.bind(lease, filmId, now)
|
|
8
|
+
.run());
|
|
9
|
+
if ((res.meta?.changes ?? 0) === 1)
|
|
10
|
+
return { won: true, lease };
|
|
11
|
+
const row = await withD1Retry(() => env.DB.prepare(`SELECT 1 AS one FROM renders WHERE job_id = ?`).bind(filmId).first());
|
|
12
|
+
return row ? { won: false } : { won: true };
|
|
13
|
+
}
|
|
14
|
+
export async function releaseFilmAdvance(env, filmId, lease) {
|
|
15
|
+
await withD1Retry(() => env.DB.prepare(`UPDATE renders SET advance_lease = NULL WHERE job_id = ? AND advance_lease = ?`)
|
|
16
|
+
.bind(filmId, lease)
|
|
17
|
+
.run());
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=film-advance-lease.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"film-advance-lease.js","sourceRoot":"","sources":["../src/film-advance-lease.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,MAAM,CAAC,MAAM,8BAA8B,GAAG,GAAG,CAAC;AAOlD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAAQ,EACR,MAAc,EACd,GAAG,GAAW,IAAI,CAAC,GAAG,EAAE;IAExB,MAAM,KAAK,GAAG,GAAG,GAAG,8BAA8B,GAAG,IAAI,CAAC;IAC1D,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,CACjC,GAAG,CAAC,EAAE,CAAC,OAAO,CACZ;uEACiE,CAClE;SACE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC;SACxB,GAAG,EAAE,CACT,CAAC;IACF,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,CAAC,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAChE,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,CACjC,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,+CAA+C,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,CACrF,CAAC;IACF,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;AAC9C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAQ,EAAE,MAAc,EAAE,KAAa;IAC9E,MAAM,WAAW,CAAC,GAAG,EAAE,CACrB,GAAG,CAAC,EAAE,CAAC,OAAO,CACZ,gFAAgF,CACjF;SACE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;SACnB,GAAG,EAAE,CACT,CAAC;AACJ,CAAC"}
|