pixi-effects 0.26.1 → 0.26.3
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/CHANGELOG.md +22 -0
- package/README.md +6 -6
- package/ai/chat-template.html +3 -3
- package/ai/tools/render.mjs +16 -11
- package/dist/Composition-3O7L2VL5.js +7 -0
- package/dist/{Composition-Z2VY3KWZ.js.map → Composition-3O7L2VL5.js.map} +1 -1
- package/dist/Composition-EE3KEE2L.cjs +16 -0
- package/dist/{Composition-THQPG4E5.cjs.map → Composition-EE3KEE2L.cjs.map} +1 -1
- package/dist/Controller.d.cts +1 -1
- package/dist/Controller.d.ts +1 -1
- package/dist/{Movie-i05Oh9mJ.d.cts → Movie-BWLLWRRe.d.cts} +10 -3
- package/dist/{Movie-DVeDTdnc.d.ts → Movie-BkP82KXS.d.ts} +10 -3
- package/dist/Presenter.d.cts +1 -1
- package/dist/Presenter.d.ts +1 -1
- package/dist/{chunk-EABL3UKJ.cjs → chunk-A225HYSY.cjs} +351 -20
- package/dist/chunk-A225HYSY.cjs.map +1 -0
- package/dist/{chunk-JHJNEIDI.js → chunk-FXAHNPBP.js} +351 -20
- package/dist/chunk-FXAHNPBP.js.map +1 -0
- package/dist/{chunk-H3RHEH5W.cjs → chunk-IIDBEYQH.cjs} +59 -59
- package/dist/{chunk-H3RHEH5W.cjs.map → chunk-IIDBEYQH.cjs.map} +1 -1
- package/dist/{chunk-YEVAUQ6Y.js → chunk-WMMZDPWK.js} +3 -3
- package/dist/{chunk-YEVAUQ6Y.js.map → chunk-WMMZDPWK.js.map} +1 -1
- package/dist/index.cjs +144 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +121 -27
- package/dist/index.js.map +1 -1
- package/dist/loader.css +1 -1
- package/dist/{music-AF4VTFAF.js → music-3O5MDQXY.js} +52 -6
- package/dist/music-3O5MDQXY.js.map +1 -0
- package/dist/{music-KB7YZLHP.cjs → music-4O367SPR.cjs} +52 -6
- package/dist/music-4O367SPR.cjs.map +1 -0
- package/dist/three.cjs +5 -5
- package/dist/three.js +1 -1
- package/docs/api.md +11 -5
- package/docs/dsl.md +4 -2
- package/llms-full.txt +21 -12
- package/package.json +1 -1
- package/skills/pixi-effects/SKILL.md +1 -1
- package/skills/pixi-effects/reference/cheatsheet.md +4 -4
- package/skills/pixi-effects/reference/pitfalls.md +1 -0
- package/skills/pixi-effects/template.html +6 -6
- package/dist/Composition-THQPG4E5.cjs +0 -16
- package/dist/Composition-Z2VY3KWZ.js +0 -7
- package/dist/chunk-EABL3UKJ.cjs.map +0 -1
- package/dist/chunk-JHJNEIDI.js.map +0 -1
- package/dist/music-AF4VTFAF.js.map +0 -1
- package/dist/music-KB7YZLHP.cjs.map +0 -1
|
@@ -373,19 +373,29 @@ function evaluateExpr(source, scope) {
|
|
|
373
373
|
// src/expr/normalizeProps.ts
|
|
374
374
|
function normalizeProps(input, scope, options = {}) {
|
|
375
375
|
const skip = new Set(options.skipKeys ?? []);
|
|
376
|
-
return walk(input, scope, skip, null);
|
|
376
|
+
return walk(input, scope, skip, null, options);
|
|
377
377
|
}
|
|
378
|
-
|
|
378
|
+
var RELATIVE = /^\s*([+-])=([\s\S]*)$/;
|
|
379
|
+
function walk(value, scope, skip, currentKey, options) {
|
|
379
380
|
if (Array.isArray(value)) {
|
|
380
|
-
return value.map((v) => walk(v, scope, skip, currentKey));
|
|
381
|
+
return value.map((v) => walk(v, scope, skip, currentKey, options));
|
|
381
382
|
}
|
|
382
383
|
if (value !== null && typeof value === "object") {
|
|
383
384
|
const out = {};
|
|
384
385
|
for (const k of Object.keys(value)) {
|
|
385
|
-
out[k] = walk(value[k], scope, skip, k);
|
|
386
|
+
out[k] = walk(value[k], scope, skip, k, options);
|
|
386
387
|
}
|
|
387
388
|
return out;
|
|
388
389
|
}
|
|
390
|
+
if (typeof value === "string" && !(currentKey !== null && skip.has(currentKey))) {
|
|
391
|
+
const rel = RELATIVE.exec(value);
|
|
392
|
+
if (rel) {
|
|
393
|
+
const n = evaluateExpr(rel[2].trim(), scope);
|
|
394
|
+
if (options.relative === "allow") return `${rel[1]}=${n}`;
|
|
395
|
+
console.warn(`pixi-effects: "${value.trim()}"${currentKey ? ` (${currentKey})` : ""} is a relative value (measured from where the layer is): it works in a keyframe's to / from / set, not in ${options.where ?? "an initial or a style"}; it is read as the number ${rel[1] === "-" ? -n : n}`);
|
|
396
|
+
return rel[1] === "-" ? -n : n;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
389
399
|
if (isExpr(value) && !(currentKey !== null && skip.has(currentKey))) {
|
|
390
400
|
if (looksLikeColorString(value)) return value;
|
|
391
401
|
return evaluateExpr(value, scope);
|
|
@@ -616,7 +626,7 @@ function applyKeyframes(timeline, target, keyframes, parentDuration, scope, skip
|
|
|
616
626
|
const kf = normalizeKeyframe(raw, parentDuration);
|
|
617
627
|
const at = offset + kf.at;
|
|
618
628
|
if (kf.kind === "set") {
|
|
619
|
-
const resolved = normalizeProps(kf.set, scope, { skipKeys });
|
|
629
|
+
const resolved = normalizeProps(kf.set, scope, { skipKeys, relative: "allow" });
|
|
620
630
|
const { rest, routed } = splitRouted(resolved, routers);
|
|
621
631
|
for (const r of routed) timeline.set(r.target, { [r.prop]: r.value }, at);
|
|
622
632
|
const { ownProps, filterProps } = partitionProps(rest);
|
|
@@ -627,7 +637,7 @@ function applyKeyframes(timeline, target, keyframes, parentDuration, scope, skip
|
|
|
627
637
|
timeline.set(f, props, at);
|
|
628
638
|
}
|
|
629
639
|
} else if (kf.kind === "to") {
|
|
630
|
-
const resolved = normalizeProps(kf.to, scope, { skipKeys });
|
|
640
|
+
const resolved = normalizeProps(kf.to, scope, { skipKeys, relative: "allow" });
|
|
631
641
|
const { rest, routed } = splitRouted(resolved, routers);
|
|
632
642
|
for (const r of routed)
|
|
633
643
|
timeline.to(r.target, { [r.prop]: r.value, duration: kf.duration, ease: kf.ease, ...kf.loop }, at);
|
|
@@ -640,7 +650,7 @@ function applyKeyframes(timeline, target, keyframes, parentDuration, scope, skip
|
|
|
640
650
|
timeline.to(f, { ...props, duration: kf.duration, ease: kf.ease, ...kf.loop }, at);
|
|
641
651
|
}
|
|
642
652
|
} else if (kf.kind === "from") {
|
|
643
|
-
const resolved = normalizeProps(kf.from, scope, { skipKeys });
|
|
653
|
+
const resolved = normalizeProps(kf.from, scope, { skipKeys, relative: "allow" });
|
|
644
654
|
const { rest, routed } = splitRouted(resolved, routers);
|
|
645
655
|
for (const r of routed)
|
|
646
656
|
timeline.from(r.target, { [r.prop]: r.value, duration: kf.duration, ease: kf.ease, ...kf.loop }, at);
|
|
@@ -653,8 +663,8 @@ function applyKeyframes(timeline, target, keyframes, parentDuration, scope, skip
|
|
|
653
663
|
timeline.from(f, { ...props, duration: kf.duration, ease: kf.ease, ...kf.loop }, at);
|
|
654
664
|
}
|
|
655
665
|
} else {
|
|
656
|
-
const fromResolved = normalizeProps(kf.from, scope, { skipKeys });
|
|
657
|
-
const toResolved = normalizeProps(kf.to, scope, { skipKeys });
|
|
666
|
+
const fromResolved = normalizeProps(kf.from, scope, { skipKeys, where: "the from side of a from + to keyframe" });
|
|
667
|
+
const toResolved = normalizeProps(kf.to, scope, { skipKeys, relative: "allow" });
|
|
658
668
|
const fromRouted = splitRouted(fromResolved, routers);
|
|
659
669
|
const toRouted = splitRouted(toResolved, routers);
|
|
660
670
|
const fromByKey = new Map(fromRouted.routed.map((r) => [r.key, r]));
|
|
@@ -704,7 +714,7 @@ function applyKeyframes(timeline, target, keyframes, parentDuration, scope, skip
|
|
|
704
714
|
}
|
|
705
715
|
function applyInitial(target, initial, scope, skipKeys = [], routers) {
|
|
706
716
|
if (!initial) return;
|
|
707
|
-
const resolved = normalizeProps(initial, scope, { skipKeys });
|
|
717
|
+
const resolved = normalizeProps(initial, scope, { skipKeys, where: "initial" });
|
|
708
718
|
const { rest, routed } = splitRouted(resolved, routers);
|
|
709
719
|
for (const r of routed) gsap.set(r.target, { [r.prop]: r.value });
|
|
710
720
|
const { ownProps, filterProps } = partitionProps(rest);
|
|
@@ -2965,7 +2975,7 @@ var AudioSequence = class extends Sequence {
|
|
|
2965
2975
|
const raw = spec;
|
|
2966
2976
|
if (raw.asset !== void 0) console.warn(`pixi-effects: ${who}: has both asset and music \u2014 the music plays; remove one`);
|
|
2967
2977
|
if (raw.sfx !== void 0) console.warn(`pixi-effects: ${who}: has both sfx and music \u2014 the music plays; remove one`);
|
|
2968
|
-
const { resolveMusic, renderMusic, musicLength, musicKey } = await import('./music-
|
|
2978
|
+
const { resolveMusic, renderMusic, musicLength, musicKey } = await import('./music-3O5MDQXY.js');
|
|
2969
2979
|
const music = resolveMusic(spec.music, who);
|
|
2970
2980
|
if (!music) return;
|
|
2971
2981
|
const natural = musicLength(music);
|
|
@@ -3042,19 +3052,57 @@ var AudioSequence = class extends Sequence {
|
|
|
3042
3052
|
};
|
|
3043
3053
|
|
|
3044
3054
|
// src/core/FrameCache.ts
|
|
3055
|
+
var BUDGET_BYTES = 96 * 1024 * 1024;
|
|
3056
|
+
var MIN_HELD = 3;
|
|
3057
|
+
var JUMP_SECONDS = 1;
|
|
3058
|
+
var EPS = 1e-6;
|
|
3059
|
+
var LOW_BEHIND = 14;
|
|
3060
|
+
var MIN_HELD_TO_READ_BACK = 20;
|
|
3061
|
+
var MIN_HELD_TO_READ_START = 9;
|
|
3062
|
+
function reportDecodeError(err, time) {
|
|
3063
|
+
const isTransient = err instanceof DOMException && (err.name === "EncodingError" || err.name === "NotSupportedError");
|
|
3064
|
+
if (isTransient) console.debug("pixi-effects: transient video decode failure at", time, "s \u2014", err.name);
|
|
3065
|
+
else console.warn("pixi-effects: video frame decode failed at", time, "s \u2014", err);
|
|
3066
|
+
}
|
|
3045
3067
|
var FrameCache = class {
|
|
3046
3068
|
sink;
|
|
3047
3069
|
capacity;
|
|
3048
3070
|
cache = /* @__PURE__ */ new Map();
|
|
3049
3071
|
_pending = /* @__PURE__ */ new Map();
|
|
3072
|
+
// Sequential mode (the sink has `samples()`): one decoder pass that runs forward as the movie asks for frame after frame.
|
|
3073
|
+
_frames = [];
|
|
3074
|
+
_cur = null;
|
|
3075
|
+
_iter = null;
|
|
3076
|
+
_next = null;
|
|
3077
|
+
_held = 0;
|
|
3078
|
+
_lock = Promise.resolve();
|
|
3079
|
+
_lastTime = null;
|
|
3080
|
+
_dir = 0;
|
|
3081
|
+
_gen = 0;
|
|
3082
|
+
_backBusy = false;
|
|
3083
|
+
_backGen = 0;
|
|
3084
|
+
_standbyBusy = false;
|
|
3085
|
+
/** A second pass at the start of the file, ready for the moment a looping video comes round. */
|
|
3086
|
+
_standby = null;
|
|
3087
|
+
_duration;
|
|
3088
|
+
_loop;
|
|
3089
|
+
/** How often it had to wait for a decoder (`restarts`, `singles`) and how many reads ahead it made: for tests and for `check`. */
|
|
3090
|
+
stats = { restarts: 0, singles: 0, prefetches: 0 };
|
|
3050
3091
|
constructor(sink, options = {}) {
|
|
3051
3092
|
this.sink = sink;
|
|
3052
3093
|
this.capacity = options.capacity ?? 30;
|
|
3094
|
+
this._duration = options.duration ?? null;
|
|
3095
|
+
this._loop = !!options.loop;
|
|
3096
|
+
}
|
|
3097
|
+
/** How many decoded frames are held right now (the sequential reader's own list, plus the exact-time cache of the old path). */
|
|
3098
|
+
get heldFrames() {
|
|
3099
|
+
return this._frames.length + this.cache.size;
|
|
3053
3100
|
}
|
|
3054
3101
|
_key(time) {
|
|
3055
3102
|
return Math.round(time * 1e6);
|
|
3056
3103
|
}
|
|
3057
3104
|
async getFrameAt(time) {
|
|
3105
|
+
if (this.sink.samples) return this._enqueue(() => this._sequentialAt(time));
|
|
3058
3106
|
const key = this._key(time);
|
|
3059
3107
|
if (this.cache.has(key)) {
|
|
3060
3108
|
const v = this.cache.get(key);
|
|
@@ -3092,14 +3140,287 @@ var FrameCache = class {
|
|
|
3092
3140
|
sample.close?.();
|
|
3093
3141
|
return frame;
|
|
3094
3142
|
} catch (err) {
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3143
|
+
reportDecodeError(err, time);
|
|
3144
|
+
return null;
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
// ── sequential mode ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
3148
|
+
/** Requests are answered one at a time, in the order they arrive: one pass cannot serve two places at once. */
|
|
3149
|
+
_enqueue(fn) {
|
|
3150
|
+
const run = this._lock.then(fn, fn);
|
|
3151
|
+
this._lock = run.then(() => void 0, () => void 0);
|
|
3152
|
+
return run;
|
|
3153
|
+
}
|
|
3154
|
+
_covering(time) {
|
|
3155
|
+
for (const e of this._frames) {
|
|
3156
|
+
if (e.end === null ? e.req !== void 0 && Math.abs(e.req - time) < EPS : e.ts <= time + EPS && time < e.end - EPS) {
|
|
3157
|
+
this._frames.splice(this._frames.indexOf(e), 1);
|
|
3158
|
+
this._frames.push(e);
|
|
3159
|
+
return e;
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
return null;
|
|
3163
|
+
}
|
|
3164
|
+
async _sequentialAt(time) {
|
|
3165
|
+
const last = this._lastTime;
|
|
3166
|
+
this._lastTime = time;
|
|
3167
|
+
if (last !== null && Math.abs(time - last) <= JUMP_SECONDS) this._dir = time > last + EPS ? 1 : time < last - EPS ? -1 : this._dir;
|
|
3168
|
+
let frame;
|
|
3169
|
+
try {
|
|
3170
|
+
frame = await this._serve(time, last);
|
|
3171
|
+
} catch (err) {
|
|
3172
|
+
reportDecodeError(err, time);
|
|
3173
|
+
await this._closeIter();
|
|
3174
|
+
return null;
|
|
3175
|
+
}
|
|
3176
|
+
this._maybeReadAhead(time);
|
|
3177
|
+
return frame;
|
|
3178
|
+
}
|
|
3179
|
+
async _serve(time, last) {
|
|
3180
|
+
const hit = this._covering(time);
|
|
3181
|
+
if (hit) return hit.frame;
|
|
3182
|
+
const sb = this._standby;
|
|
3183
|
+
if (sb && time >= sb.cur.ts - EPS && time - sb.cur.ts <= JUMP_SECONDS && (!this._iter || !this._cur || this._cur.ts > time + JUMP_SECONDS)) return await this._adoptStandby(time);
|
|
3184
|
+
if (last === null || Math.abs(time - last) > JUMP_SECONDS) return await this._single(time);
|
|
3185
|
+
const cur = this._cur;
|
|
3186
|
+
if (this._iter && cur && time >= cur.ts && time - cur.ts <= JUMP_SECONDS) return await this._advance(time);
|
|
3187
|
+
const backwards = !!cur && time < cur.ts;
|
|
3188
|
+
return await this._restart(time, backwards ? Math.max(0, time - Math.max(1, this._held - 1) / 30) : time);
|
|
3189
|
+
}
|
|
3190
|
+
// ── reading ahead ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
3191
|
+
_maybeReadAhead(time) {
|
|
3192
|
+
if (this._held === 0) return;
|
|
3193
|
+
if (this._dir === -1) {
|
|
3194
|
+
this._dropStandby();
|
|
3195
|
+
let behind = 0, passed = 0, earliest = Infinity;
|
|
3196
|
+
for (const e of this._frames) {
|
|
3197
|
+
if (e.ts < time - EPS) behind++;
|
|
3198
|
+
else if (e.ts > time + EPS) passed++;
|
|
3199
|
+
if (e.ts < earliest) earliest = e.ts;
|
|
3200
|
+
}
|
|
3201
|
+
if (this._held >= MIN_HELD_TO_READ_BACK && behind < LOW_BEHIND && passed >= 3 && earliest > EPS && !this._backBusy) void this._readBack(earliest, passed);
|
|
3202
|
+
} else if (this._held >= MIN_HELD_TO_READ_START && this._dir === 1 && this._loop && this._duration !== null && time >= this._duration - JUMP_SECONDS && !this._standby && !this._standbyBusy) {
|
|
3203
|
+
void this._readStart();
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
/** In the background: the window of frames before the earliest one held (reverse play comes to it next). */
|
|
3207
|
+
async _readBack(earliest, room) {
|
|
3208
|
+
this._backBusy = true;
|
|
3209
|
+
this.stats.prefetches++;
|
|
3210
|
+
const gen = this._gen, bg = this._backGen;
|
|
3211
|
+
const want = Math.max(3, Math.min(Math.floor(this._held * 0.6), room));
|
|
3212
|
+
let iter = null;
|
|
3213
|
+
const got = [];
|
|
3214
|
+
try {
|
|
3215
|
+
iter = this.sink.samples(Math.max(0, earliest - want / 30));
|
|
3216
|
+
for (; ; ) {
|
|
3217
|
+
const r = await iter.next();
|
|
3218
|
+
if (r.done) break;
|
|
3219
|
+
if (gen !== this._gen || r.value.timestamp >= earliest - EPS) {
|
|
3220
|
+
r.value.close?.();
|
|
3221
|
+
break;
|
|
3222
|
+
}
|
|
3223
|
+
got.push(r.value);
|
|
3224
|
+
if (bg !== this._backGen) break;
|
|
3225
|
+
}
|
|
3226
|
+
if (gen === this._gen && bg === this._backGen) {
|
|
3227
|
+
for (let k = 0; k < got.length; k++) {
|
|
3228
|
+
const s = got[k];
|
|
3229
|
+
const frame = s.toVideoFrame();
|
|
3230
|
+
this._keep({ ts: s.timestamp, end: got[k + 1]?.timestamp ?? earliest, frame }, frame);
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
} catch (err) {
|
|
3234
|
+
reportDecodeError(err, earliest);
|
|
3235
|
+
} finally {
|
|
3236
|
+
for (const s of got) s.close?.();
|
|
3237
|
+
this._backBusy = false;
|
|
3238
|
+
try {
|
|
3239
|
+
await iter?.return?.(void 0);
|
|
3240
|
+
} catch {
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
/** In the background: the first frames of the file and a pass that stands behind them; held (pinned) until the loop comes round. */
|
|
3245
|
+
async _readStart() {
|
|
3246
|
+
this._standbyBusy = true;
|
|
3247
|
+
this.stats.prefetches++;
|
|
3248
|
+
const gen = this._gen;
|
|
3249
|
+
const want = Math.max(3, Math.min(10, Math.floor(this._held / 3)));
|
|
3250
|
+
let iter = null;
|
|
3251
|
+
const got = [];
|
|
3252
|
+
let keep = false;
|
|
3253
|
+
try {
|
|
3254
|
+
iter = this.sink.samples(0);
|
|
3255
|
+
for (; got.length < want + 1; ) {
|
|
3256
|
+
const r = await iter.next();
|
|
3257
|
+
if (r.done) break;
|
|
3258
|
+
if (gen !== this._gen) {
|
|
3259
|
+
r.value.close?.();
|
|
3260
|
+
break;
|
|
3261
|
+
}
|
|
3262
|
+
got.push(r.value);
|
|
3263
|
+
}
|
|
3264
|
+
if (gen === this._gen && got.length >= 2) {
|
|
3265
|
+
const next = got.pop();
|
|
3266
|
+
let cur = null;
|
|
3267
|
+
got.forEach((s, k) => {
|
|
3268
|
+
const frame = s.toVideoFrame();
|
|
3269
|
+
s.close?.();
|
|
3270
|
+
const entry = { ts: s.timestamp, end: (got[k + 1] ?? next).timestamp, frame, pinned: true };
|
|
3271
|
+
this._keep(entry, frame);
|
|
3272
|
+
cur = entry;
|
|
3273
|
+
});
|
|
3274
|
+
got.length = 0;
|
|
3275
|
+
if (cur) {
|
|
3276
|
+
this._standby = { iter, cur, next };
|
|
3277
|
+
keep = true;
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
} catch (err) {
|
|
3281
|
+
reportDecodeError(err, 0);
|
|
3282
|
+
} finally {
|
|
3283
|
+
for (const s of got) s.close?.();
|
|
3284
|
+
this._standbyBusy = false;
|
|
3285
|
+
if (!keep) {
|
|
3286
|
+
try {
|
|
3287
|
+
await iter?.return?.(void 0);
|
|
3288
|
+
} catch {
|
|
3289
|
+
}
|
|
3100
3290
|
}
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
async _adoptStandby(time) {
|
|
3294
|
+
const sb = this._standby;
|
|
3295
|
+
this._standby = null;
|
|
3296
|
+
await this._closeIter();
|
|
3297
|
+
for (const e of this._frames) e.pinned = false;
|
|
3298
|
+
this._iter = sb.iter;
|
|
3299
|
+
this._next = sb.next;
|
|
3300
|
+
this._cur = sb.cur;
|
|
3301
|
+
return this._advance(time);
|
|
3302
|
+
}
|
|
3303
|
+
_dropStandby() {
|
|
3304
|
+
const sb = this._standby;
|
|
3305
|
+
if (!sb) return;
|
|
3306
|
+
this._standby = null;
|
|
3307
|
+
sb.next?.close?.();
|
|
3308
|
+
for (const e of this._frames.filter((x) => x.pinned)) {
|
|
3309
|
+
e.pinned = false;
|
|
3310
|
+
}
|
|
3311
|
+
void sb.iter.return?.(void 0);
|
|
3312
|
+
}
|
|
3313
|
+
/** One frame by itself (a seek): getSample, kept so that asking again for the same time costs nothing. The running pass, if any, is closed: it no longer follows the playhead. */
|
|
3314
|
+
async _single(time) {
|
|
3315
|
+
this.stats.singles++;
|
|
3316
|
+
await this._closeIter();
|
|
3317
|
+
this._cur = null;
|
|
3318
|
+
const sample = await this.sink.getSample(time);
|
|
3319
|
+
if (!sample) return null;
|
|
3320
|
+
const frame = sample.toVideoFrame();
|
|
3321
|
+
sample.close?.();
|
|
3322
|
+
this._keep({ ts: sample.timestamp, end: null, frame, req: time }, frame);
|
|
3323
|
+
return frame;
|
|
3324
|
+
}
|
|
3325
|
+
/** Start a pass at `time` (a seek and the frames of one group of pictures), keeping what is still held for a step back. */
|
|
3326
|
+
async _restart(time, from = time) {
|
|
3327
|
+
this.stats.restarts++;
|
|
3328
|
+
this._backGen++;
|
|
3329
|
+
await this._closeIter();
|
|
3330
|
+
this._cur = null;
|
|
3331
|
+
const iter = this.sink.samples(from);
|
|
3332
|
+
this._iter = iter;
|
|
3333
|
+
const first = await iter.next();
|
|
3334
|
+
if (first.done) {
|
|
3335
|
+
await this._closeIter();
|
|
3101
3336
|
return null;
|
|
3102
3337
|
}
|
|
3338
|
+
if (first.value.timestamp > time + EPS) {
|
|
3339
|
+
first.value.close?.();
|
|
3340
|
+
await this._closeIter();
|
|
3341
|
+
return null;
|
|
3342
|
+
}
|
|
3343
|
+
this._take(first.value);
|
|
3344
|
+
return this._advance(time);
|
|
3345
|
+
}
|
|
3346
|
+
/** Run the pass forward until the frame that covers `time` is the current one; the next frame is read ahead (its start is where this one ends). */
|
|
3347
|
+
async _advance(time) {
|
|
3348
|
+
const cur = () => this._cur;
|
|
3349
|
+
for (; ; ) {
|
|
3350
|
+
if (!this._next) {
|
|
3351
|
+
let r;
|
|
3352
|
+
try {
|
|
3353
|
+
r = await this._iter.next();
|
|
3354
|
+
} catch (err) {
|
|
3355
|
+
reportDecodeError(err, time);
|
|
3356
|
+
await this._closeIter();
|
|
3357
|
+
const prev = this._frames.length > 1 ? this._frames[this._frames.length - 2] : null;
|
|
3358
|
+
const spacing = prev ? cur().ts - prev.ts : 1 / 24;
|
|
3359
|
+
return time < cur().ts + spacing - EPS ? cur().frame : null;
|
|
3360
|
+
}
|
|
3361
|
+
if (r.done) {
|
|
3362
|
+
cur().end = Infinity;
|
|
3363
|
+
this._iter = null;
|
|
3364
|
+
break;
|
|
3365
|
+
}
|
|
3366
|
+
this._next = r.value;
|
|
3367
|
+
}
|
|
3368
|
+
cur().end = this._next.timestamp;
|
|
3369
|
+
if (this._next.timestamp <= time + EPS) {
|
|
3370
|
+
const s = this._next;
|
|
3371
|
+
this._next = null;
|
|
3372
|
+
this._take(s);
|
|
3373
|
+
} else break;
|
|
3374
|
+
}
|
|
3375
|
+
return cur().frame;
|
|
3376
|
+
}
|
|
3377
|
+
_take(sample) {
|
|
3378
|
+
const frame = sample.toVideoFrame();
|
|
3379
|
+
sample.close?.();
|
|
3380
|
+
const entry = { ts: sample.timestamp, end: null, frame };
|
|
3381
|
+
this._keep(entry, frame);
|
|
3382
|
+
this._cur = entry;
|
|
3383
|
+
}
|
|
3384
|
+
/** Hold a frame (the first one sets how many fit: a byte budget, not a count) and close the oldest beyond that; the frame the pass stands on is never closed. */
|
|
3385
|
+
_keep(entry, frame) {
|
|
3386
|
+
if (this._held === 0) {
|
|
3387
|
+
const bytes = frame.allocationSize?.() ?? frame.displayWidth * frame.displayHeight * 1.5;
|
|
3388
|
+
this._held = Math.max(MIN_HELD, Math.min(this.capacity, Math.floor(BUDGET_BYTES / Math.max(1, bytes))));
|
|
3389
|
+
}
|
|
3390
|
+
this._frames.push(entry);
|
|
3391
|
+
while (this._frames.length > this._held) {
|
|
3392
|
+
const victim = this._victim(entry);
|
|
3393
|
+
if (!victim) break;
|
|
3394
|
+
this._frames.splice(this._frames.indexOf(victim), 1);
|
|
3395
|
+
victim.frame.close?.();
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
/**
|
|
3399
|
+
* The frame to let go: one the playhead has already passed (going forward those before it, going back those after it), the furthest
|
|
3400
|
+
* behind first; when there is none, the one furthest ahead of the playhead (the last to be needed). Least recently used when the
|
|
3401
|
+
* direction is not known. Frames of the standby (pinned) and the frame the pass stands on are never chosen.
|
|
3402
|
+
*/
|
|
3403
|
+
_victim(except) {
|
|
3404
|
+
const cand = this._frames.filter((e) => e !== this._cur && e !== except && !e.pinned);
|
|
3405
|
+
if (cand.length === 0) return null;
|
|
3406
|
+
if (this._dir === 0) return cand[0];
|
|
3407
|
+
const at = this._lastTime ?? 0;
|
|
3408
|
+
const passed = cand.filter((e) => this._dir === 1 ? e.ts < at - EPS : e.ts > at + EPS);
|
|
3409
|
+
const farthest = (list, sign) => list.reduce((a, b) => sign * (b.ts - a.ts) > 0 ? b : a);
|
|
3410
|
+
if (passed.length) return farthest(passed, this._dir === 1 ? -1 : 1);
|
|
3411
|
+
return farthest(cand, this._dir === 1 ? 1 : -1);
|
|
3412
|
+
}
|
|
3413
|
+
async _closeIter() {
|
|
3414
|
+
const iter = this._iter;
|
|
3415
|
+
this._iter = null;
|
|
3416
|
+
this._next?.close?.();
|
|
3417
|
+
this._next = null;
|
|
3418
|
+
if (iter) {
|
|
3419
|
+
try {
|
|
3420
|
+
await iter.return?.(void 0);
|
|
3421
|
+
} catch {
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3103
3424
|
}
|
|
3104
3425
|
_evictIfNeeded() {
|
|
3105
3426
|
while (this.cache.size > this.capacity) {
|
|
@@ -3111,9 +3432,19 @@ var FrameCache = class {
|
|
|
3111
3432
|
}
|
|
3112
3433
|
}
|
|
3113
3434
|
dispose() {
|
|
3435
|
+
this._gen++;
|
|
3436
|
+
this._dropStandby();
|
|
3114
3437
|
for (const f of this.cache.values()) f?.close?.();
|
|
3115
3438
|
this.cache.clear();
|
|
3116
3439
|
this._pending.clear();
|
|
3440
|
+
for (const e of this._frames) e.frame.close?.();
|
|
3441
|
+
this._frames = [];
|
|
3442
|
+
this._cur = null;
|
|
3443
|
+
this._next?.close?.();
|
|
3444
|
+
this._next = null;
|
|
3445
|
+
const iter = this._iter;
|
|
3446
|
+
this._iter = null;
|
|
3447
|
+
void iter?.return?.(void 0);
|
|
3117
3448
|
}
|
|
3118
3449
|
};
|
|
3119
3450
|
|
|
@@ -3144,7 +3475,7 @@ var VideoSequence = class extends Sequence {
|
|
|
3144
3475
|
this._sourceDuration = data.duration;
|
|
3145
3476
|
this._remap = remapOf(this.spec, "currentTime", this._sourceDuration);
|
|
3146
3477
|
this._audioBuffer = this.spec.audio !== false ? data.audioBuffer : null;
|
|
3147
|
-
this._cache = new FrameCache(data.sink, { capacity: 30 });
|
|
3478
|
+
this._cache = new FrameCache(data.sink, { capacity: 30, duration: data.duration, loop: !!this.spec.loop });
|
|
3148
3479
|
const probeFrame = await this._cache.getFrameAt(0);
|
|
3149
3480
|
const w = probeFrame?.displayWidth ?? 1920;
|
|
3150
3481
|
const h = probeFrame?.displayHeight ?? 1080;
|
|
@@ -4344,7 +4675,7 @@ function resolveDim(v, scope) {
|
|
|
4344
4675
|
var _CompositionSequence = null;
|
|
4345
4676
|
async function getCompositionSequence() {
|
|
4346
4677
|
if (!_CompositionSequence) {
|
|
4347
|
-
_CompositionSequence = (await import('./Composition-
|
|
4678
|
+
_CompositionSequence = (await import('./Composition-3O7L2VL5.js')).CompositionSequence;
|
|
4348
4679
|
}
|
|
4349
4680
|
return _CompositionSequence;
|
|
4350
4681
|
}
|
|
@@ -4387,5 +4718,5 @@ async function buildSequenceTree(specs, parent, root) {
|
|
|
4387
4718
|
}
|
|
4388
4719
|
|
|
4389
4720
|
export { CameraSequence, DEFAULT_FOV, DEG2 as DEG, FALLOFFS, LIGHT_KINDS, LIGHT_PROPS, LightSequence, MANY_BLURRED, MAX_BLUR, MAX_CASTERS, MAX_LIGHTS, MIN_BLUR, NEAR, NullSequence, SHADOW_TAPS, Sequence, bindClock, blurRadius, buildScope, buildSequenceTree, cameraBasis, checkEase, clockTable, contentLength, ensureFilterLibrary, evaluateExpr, flattenSvgPath, focusProblems, homeCamera, homeDistance, layerInitialZ, layerToWorld, lightLevel, noise, packLights, projectLayer, projectPoint, rand, random, registerSequenceType, remapOf, resolveAt, timeRemapOf };
|
|
4390
|
-
//# sourceMappingURL=chunk-
|
|
4391
|
-
//# sourceMappingURL=chunk-
|
|
4721
|
+
//# sourceMappingURL=chunk-FXAHNPBP.js.map
|
|
4722
|
+
//# sourceMappingURL=chunk-FXAHNPBP.js.map
|