pixi-effects 0.26.1 → 0.26.2

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +6 -6
  3. package/ai/chat-template.html +3 -3
  4. package/dist/Composition-H4QA24MV.js +7 -0
  5. package/dist/{Composition-Z2VY3KWZ.js.map → Composition-H4QA24MV.js.map} +1 -1
  6. package/dist/Composition-HM4YT5L7.cjs +16 -0
  7. package/dist/{Composition-THQPG4E5.cjs.map → Composition-HM4YT5L7.cjs.map} +1 -1
  8. package/dist/{chunk-H3RHEH5W.cjs → chunk-555URQHQ.cjs} +59 -59
  9. package/dist/{chunk-H3RHEH5W.cjs.map → chunk-555URQHQ.cjs.map} +1 -1
  10. package/dist/{chunk-YEVAUQ6Y.js → chunk-GBUM7X2S.js} +3 -3
  11. package/dist/{chunk-YEVAUQ6Y.js.map → chunk-GBUM7X2S.js.map} +1 -1
  12. package/dist/{chunk-EABL3UKJ.cjs → chunk-WPXDRSGN.cjs} +162 -9
  13. package/dist/chunk-WPXDRSGN.cjs.map +1 -0
  14. package/dist/{chunk-JHJNEIDI.js → chunk-X3HB4DSR.js} +162 -9
  15. package/dist/chunk-X3HB4DSR.js.map +1 -0
  16. package/dist/index.cjs +29 -29
  17. package/dist/index.js +5 -5
  18. package/dist/loader.css +1 -1
  19. package/dist/{music-AF4VTFAF.js → music-3O5MDQXY.js} +52 -6
  20. package/dist/music-3O5MDQXY.js.map +1 -0
  21. package/dist/{music-KB7YZLHP.cjs → music-4O367SPR.cjs} +52 -6
  22. package/dist/music-4O367SPR.cjs.map +1 -0
  23. package/dist/three.cjs +5 -5
  24. package/dist/three.js +1 -1
  25. package/docs/dsl.md +2 -2
  26. package/llms-full.txt +5 -4
  27. package/package.json +1 -1
  28. package/skills/pixi-effects/SKILL.md +1 -1
  29. package/skills/pixi-effects/reference/cheatsheet.md +1 -1
  30. package/skills/pixi-effects/reference/pitfalls.md +1 -0
  31. package/skills/pixi-effects/template.html +6 -6
  32. package/dist/Composition-THQPG4E5.cjs +0 -16
  33. package/dist/Composition-Z2VY3KWZ.js +0 -7
  34. package/dist/chunk-EABL3UKJ.cjs.map +0 -1
  35. package/dist/chunk-JHJNEIDI.js.map +0 -1
  36. package/dist/music-AF4VTFAF.js.map +0 -1
  37. package/dist/music-KB7YZLHP.cjs.map +0 -1
@@ -2986,7 +2986,7 @@ var AudioSequence = class extends Sequence {
2986
2986
  const raw = spec;
2987
2987
  if (raw.asset !== void 0) console.warn(`pixi-effects: ${who}: has both asset and music \u2014 the music plays; remove one`);
2988
2988
  if (raw.sfx !== void 0) console.warn(`pixi-effects: ${who}: has both sfx and music \u2014 the music plays; remove one`);
2989
- const { resolveMusic, renderMusic, musicLength, musicKey } = await import('./music-KB7YZLHP.cjs');
2989
+ const { resolveMusic, renderMusic, musicLength, musicKey } = await import('./music-4O367SPR.cjs');
2990
2990
  const music = resolveMusic(spec.music, who);
2991
2991
  if (!music) return;
2992
2992
  const natural = musicLength(music);
@@ -3063,19 +3063,41 @@ var AudioSequence = class extends Sequence {
3063
3063
  };
3064
3064
 
3065
3065
  // src/core/FrameCache.ts
3066
+ var BUDGET_BYTES = 96 * 1024 * 1024;
3067
+ var MIN_HELD = 3;
3068
+ var JUMP_SECONDS = 1;
3069
+ var EPS = 1e-6;
3070
+ function reportDecodeError(err, time) {
3071
+ const isTransient = err instanceof DOMException && (err.name === "EncodingError" || err.name === "NotSupportedError");
3072
+ if (isTransient) console.debug("pixi-effects: transient video decode failure at", time, "s \u2014", err.name);
3073
+ else console.warn("pixi-effects: video frame decode failed at", time, "s \u2014", err);
3074
+ }
3066
3075
  var FrameCache = class {
3067
3076
  sink;
3068
3077
  capacity;
3069
3078
  cache = /* @__PURE__ */ new Map();
3070
3079
  _pending = /* @__PURE__ */ new Map();
3080
+ // Sequential mode (the sink has `samples()`): one decoder pass that runs forward as the movie asks for frame after frame.
3081
+ _frames = [];
3082
+ _cur = null;
3083
+ _iter = null;
3084
+ _next = null;
3085
+ _held = 0;
3086
+ _lock = Promise.resolve();
3087
+ _lastTime = null;
3071
3088
  constructor(sink, options = {}) {
3072
3089
  this.sink = sink;
3073
3090
  this.capacity = options.capacity ?? 30;
3074
3091
  }
3092
+ /** How many decoded frames are held right now (the sequential reader's own list, plus the exact-time cache of the old path). */
3093
+ get heldFrames() {
3094
+ return this._frames.length + this.cache.size;
3095
+ }
3075
3096
  _key(time) {
3076
3097
  return Math.round(time * 1e6);
3077
3098
  }
3078
3099
  async getFrameAt(time) {
3100
+ if (this.sink.samples) return this._enqueue(() => this._sequentialAt(time));
3079
3101
  const key = this._key(time);
3080
3102
  if (this.cache.has(key)) {
3081
3103
  const v = this.cache.get(key);
@@ -3113,15 +3135,138 @@ var FrameCache = class {
3113
3135
  sample.close?.();
3114
3136
  return frame;
3115
3137
  } catch (err) {
3116
- const isTransient = err instanceof DOMException && (err.name === "EncodingError" || err.name === "NotSupportedError");
3117
- if (isTransient) {
3118
- console.debug("pixi-effects: transient video decode failure at", time, "s \u2014", err.name);
3119
- } else {
3120
- console.warn("pixi-effects: video frame decode failed at", time, "s \u2014", err);
3138
+ reportDecodeError(err, time);
3139
+ return null;
3140
+ }
3141
+ }
3142
+ // ── sequential mode ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
3143
+ /** Requests are answered one at a time, in the order they arrive: one pass cannot serve two places at once. */
3144
+ _enqueue(fn) {
3145
+ const run = this._lock.then(fn, fn);
3146
+ this._lock = run.then(() => void 0, () => void 0);
3147
+ return run;
3148
+ }
3149
+ _covering(time) {
3150
+ for (const e of this._frames) {
3151
+ if (e.end === null ? e.req !== void 0 && Math.abs(e.req - time) < EPS : e.ts <= time + EPS && time < e.end - EPS) {
3152
+ this._frames.splice(this._frames.indexOf(e), 1);
3153
+ this._frames.push(e);
3154
+ return e;
3121
3155
  }
3156
+ }
3157
+ return null;
3158
+ }
3159
+ async _sequentialAt(time) {
3160
+ try {
3161
+ const last = this._lastTime;
3162
+ this._lastTime = time;
3163
+ const hit = this._covering(time);
3164
+ if (hit) return hit.frame;
3165
+ if (last === null || Math.abs(time - last) > JUMP_SECONDS) return await this._single(time);
3166
+ const cur = this._cur;
3167
+ if (this._iter && cur && time >= cur.ts && time - cur.ts <= JUMP_SECONDS) return await this._advance(time);
3168
+ const backwards = !!cur && time < cur.ts;
3169
+ return await this._restart(time, backwards ? Math.max(0, time - this._held / 30) : time);
3170
+ } catch (err) {
3171
+ reportDecodeError(err, time);
3172
+ await this._closeIter();
3122
3173
  return null;
3123
3174
  }
3124
3175
  }
3176
+ /** 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. */
3177
+ async _single(time) {
3178
+ await this._closeIter();
3179
+ this._cur = null;
3180
+ const sample = await this.sink.getSample(time);
3181
+ if (!sample) return null;
3182
+ const frame = sample.toVideoFrame();
3183
+ sample.close?.();
3184
+ this._keep({ ts: sample.timestamp, end: null, frame, req: time }, frame);
3185
+ return frame;
3186
+ }
3187
+ /** Start a pass at `time` (a seek and the frames of one group of pictures), keeping what is still held for a step back. */
3188
+ async _restart(time, from = time) {
3189
+ await this._closeIter();
3190
+ this._cur = null;
3191
+ const iter = this.sink.samples(from);
3192
+ this._iter = iter;
3193
+ const first = await iter.next();
3194
+ if (first.done) {
3195
+ await this._closeIter();
3196
+ return null;
3197
+ }
3198
+ if (first.value.timestamp > time + EPS) {
3199
+ first.value.close?.();
3200
+ await this._closeIter();
3201
+ return null;
3202
+ }
3203
+ this._take(first.value);
3204
+ return this._advance(time);
3205
+ }
3206
+ /** 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). */
3207
+ async _advance(time) {
3208
+ const cur = () => this._cur;
3209
+ for (; ; ) {
3210
+ if (!this._next) {
3211
+ let r;
3212
+ try {
3213
+ r = await this._iter.next();
3214
+ } catch (err) {
3215
+ reportDecodeError(err, time);
3216
+ await this._closeIter();
3217
+ const prev = this._frames.length > 1 ? this._frames[this._frames.length - 2] : null;
3218
+ const spacing = prev ? cur().ts - prev.ts : 1 / 24;
3219
+ return time < cur().ts + spacing - EPS ? cur().frame : null;
3220
+ }
3221
+ if (r.done) {
3222
+ cur().end = Infinity;
3223
+ this._iter = null;
3224
+ break;
3225
+ }
3226
+ this._next = r.value;
3227
+ }
3228
+ cur().end = this._next.timestamp;
3229
+ if (this._next.timestamp <= time + EPS) {
3230
+ const s = this._next;
3231
+ this._next = null;
3232
+ this._take(s);
3233
+ } else break;
3234
+ }
3235
+ return cur().frame;
3236
+ }
3237
+ _take(sample) {
3238
+ const frame = sample.toVideoFrame();
3239
+ sample.close?.();
3240
+ const entry = { ts: sample.timestamp, end: null, frame };
3241
+ this._keep(entry, frame);
3242
+ this._cur = entry;
3243
+ }
3244
+ /** 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. */
3245
+ _keep(entry, frame) {
3246
+ if (this._held === 0) {
3247
+ const bytes = frame.allocationSize?.() ?? frame.displayWidth * frame.displayHeight * 1.5;
3248
+ this._held = Math.max(MIN_HELD, Math.min(this.capacity, Math.floor(BUDGET_BYTES / Math.max(1, bytes))));
3249
+ }
3250
+ this._frames.push(entry);
3251
+ while (this._frames.length > this._held) {
3252
+ const oldest = this._frames.find((e) => e !== this._cur && e !== entry);
3253
+ if (!oldest) break;
3254
+ this._frames.splice(this._frames.indexOf(oldest), 1);
3255
+ oldest.frame.close?.();
3256
+ }
3257
+ }
3258
+ async _closeIter() {
3259
+ const iter = this._iter;
3260
+ this._iter = null;
3261
+ this._next?.close?.();
3262
+ this._next = null;
3263
+ if (iter) {
3264
+ try {
3265
+ await iter.return?.(void 0);
3266
+ } catch {
3267
+ }
3268
+ }
3269
+ }
3125
3270
  _evictIfNeeded() {
3126
3271
  while (this.cache.size > this.capacity) {
3127
3272
  const oldestKey = this.cache.keys().next().value;
@@ -3135,6 +3280,14 @@ var FrameCache = class {
3135
3280
  for (const f of this.cache.values()) f?.close?.();
3136
3281
  this.cache.clear();
3137
3282
  this._pending.clear();
3283
+ for (const e of this._frames) e.frame.close?.();
3284
+ this._frames = [];
3285
+ this._cur = null;
3286
+ this._next?.close?.();
3287
+ this._next = null;
3288
+ const iter = this._iter;
3289
+ this._iter = null;
3290
+ void iter?.return?.(void 0);
3138
3291
  }
3139
3292
  };
3140
3293
 
@@ -4365,7 +4518,7 @@ function resolveDim(v, scope) {
4365
4518
  var _CompositionSequence = null;
4366
4519
  async function getCompositionSequence() {
4367
4520
  if (!_CompositionSequence) {
4368
- _CompositionSequence = (await import('./Composition-THQPG4E5.cjs')).CompositionSequence;
4521
+ _CompositionSequence = (await import('./Composition-HM4YT5L7.cjs')).CompositionSequence;
4369
4522
  }
4370
4523
  return _CompositionSequence;
4371
4524
  }
@@ -4450,5 +4603,5 @@ exports.registerSequenceType = registerSequenceType;
4450
4603
  exports.remapOf = remapOf;
4451
4604
  exports.resolveAt = resolveAt;
4452
4605
  exports.timeRemapOf = timeRemapOf;
4453
- //# sourceMappingURL=chunk-EABL3UKJ.cjs.map
4454
- //# sourceMappingURL=chunk-EABL3UKJ.cjs.map
4606
+ //# sourceMappingURL=chunk-WPXDRSGN.cjs.map
4607
+ //# sourceMappingURL=chunk-WPXDRSGN.cjs.map