@pixerate/editor 0.253.0 → 0.254.1

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 (33) hide show
  1. package/dist/animation-editor/index.cjs +53 -53
  2. package/dist/animation-editor/index.js +2 -2
  3. package/dist/canvas/changeFeed.d.cts +6 -0
  4. package/dist/canvas/changeFeed.d.ts +6 -0
  5. package/dist/canvas/index.cjs +16 -9
  6. package/dist/canvas/index.js +10 -3
  7. package/dist/{chunk-QKXO6JPP.cjs → chunk-2OQGPEHH.cjs} +14 -14
  8. package/dist/{chunk-Z3BRH4JK.cjs → chunk-557BDWKO.cjs} +6 -6
  9. package/dist/{chunk-ZBCT5MEA.cjs → chunk-CBVNCWJM.cjs} +7 -7
  10. package/dist/{chunk-226QDRO3.js → chunk-DHQPRKXF.js} +1 -1
  11. package/dist/{chunk-ABL7IWFZ.js → chunk-GBEF2D4P.js} +1 -1
  12. package/dist/{chunk-ISIO63Q5.cjs → chunk-L2JTLW6B.cjs} +150 -25
  13. package/dist/{chunk-JA57JIT5.js → chunk-MV7CYFMJ.js} +133 -8
  14. package/dist/{chunk-5E2Q7ZHI.js → chunk-MWUZCEFT.js} +1 -1
  15. package/dist/effects/conformance.d.cts +1 -0
  16. package/dist/effects/conformance.d.ts +1 -0
  17. package/dist/effects/fake.d.cts +7 -1
  18. package/dist/effects/fake.d.ts +7 -1
  19. package/dist/effects/helpers.d.cts +36 -4
  20. package/dist/effects/helpers.d.ts +36 -4
  21. package/dist/effects/index.cjs +331 -102
  22. package/dist/effects/index.js +238 -9
  23. package/dist/effects/types.d.cts +84 -5
  24. package/dist/effects/types.d.ts +84 -5
  25. package/dist/image-editor/index.cjs +15 -15
  26. package/dist/image-editor/index.js +1 -1
  27. package/dist/paint-editor/index.cjs +7 -7
  28. package/dist/paint-editor/index.js +2 -2
  29. package/dist/vector/index.cjs +6 -6
  30. package/dist/vector/index.js +1 -1
  31. package/dist/video-editor/index.cjs +344 -344
  32. package/dist/video-editor/index.js +4 -4
  33. package/package.json +1 -1
@@ -3,11 +3,15 @@ import {
3
3
  } from "./chunk-V2ZCJRDM.js";
4
4
 
5
5
  // src/effects/types.ts
6
- var EFFECT_KINDS = ["shader", "vector", "motion", "camera"];
7
- var EFFECT_USES = ["filter", "transition", "generator", "overlay", "title", "motion", "interactive", "camera"];
6
+ var EFFECT_KINDS = ["shader", "vector", "motion", "camera", "timing"];
7
+ var EFFECT_USES = ["filter", "transition", "generator", "overlay", "title", "motion", "interactive", "camera", "timing"];
8
8
  var CAMERA_MOVE_ROLES = ["transition", "hold"];
9
9
  var EFFECT_ACCEPTS = ["path"];
10
10
  var EFFECT_PATH_FORMAT = "svg-path";
11
+ var TIMING_DEFAULT_FPS = 30;
12
+ var TIMING_MAX_FRAMES = 128;
13
+ var TIMING_MAX_FPS = 240;
14
+ var TIMING_BLENDS = ["average", "lighten"];
11
15
  var EFFECT_SUBJECT_KINDS = ["image", "video", "svg", "text", "slot"];
12
16
  var EFFECT_LAYER_ROLES = ["background", "subject", "overlay"];
13
17
  var EFFECT_TRANSITION_MS = 800;
@@ -87,10 +91,11 @@ function resolveEffectValues(description, values) {
87
91
  if (errors.length) throw new EffectControlError(description.id, errors);
88
92
  return { ...description.defaults, ...values ?? {} };
89
93
  }
94
+ var PIXELLESS_KINDS = ["motion", "camera", "timing"];
90
95
  function effectOffered(e, uses) {
91
- if (!uses) return e.kind !== "motion" && e.kind !== "camera";
92
- if (e.kind === "motion" && !uses.includes("motion")) return false;
93
- if (e.kind === "camera" && !uses.includes("camera")) return false;
96
+ const pixelless = PIXELLESS_KINDS.includes(e.kind);
97
+ if (!uses) return !pixelless;
98
+ if (pixelless && !uses.includes(e.kind)) return false;
94
99
  return e.uses.some((u) => uses.includes(u));
95
100
  }
96
101
  function normaliseEffectTag(tag) {
@@ -299,6 +304,117 @@ function cameraBoxOfView(view, stage) {
299
304
  const height = stage.height / view.zoom;
300
305
  return { x: -view.scrollX, y: -view.scrollY, width, height };
301
306
  }
307
+ function checkTimingFootage(footageMs, id) {
308
+ if (typeof footageMs !== "number" || !Number.isFinite(footageMs) || footageMs <= 0) {
309
+ throw new EffectUnsupportedError(`The footage's length is a number of ms above 0; it was ${String(footageMs)}.`, id);
310
+ }
311
+ }
312
+ function checkTimingRequest(request, id) {
313
+ if (!request || typeof request !== "object") throw new EffectUnsupportedError("A timing request is { time, footage, fps? }: ms into the retimed clip, the footage's length in ms, and its frame rate.", id);
314
+ checkTimingFootage(request.footage, id);
315
+ const fps = request.fps ?? TIMING_DEFAULT_FPS;
316
+ if (typeof fps !== "number" || !Number.isFinite(fps) || fps <= 0 || fps > TIMING_MAX_FPS) {
317
+ throw new EffectUnsupportedError(`The footage's frame rate is above 0 and at most ${TIMING_MAX_FPS} frames per second; it was ${String(fps)}.`, id);
318
+ }
319
+ if (typeof request.time !== "number" || !Number.isFinite(request.time)) throw new EffectUnsupportedError(`The time is a number of ms into the retimed clip; it was ${String(request.time)}.`, id);
320
+ return fps;
321
+ }
322
+ var lastFrameOf = (footageMs, fps) => Math.max(0, Math.ceil(footageMs * fps / 1e3 - 1e-9) - 1);
323
+ function normaliseTimingSample(raw, request, blend = "average") {
324
+ const fps = request.fps ?? TIMING_DEFAULT_FPS;
325
+ const frameMs = 1e3 / fps;
326
+ const last = lastFrameOf(request.footage, fps);
327
+ const frameOf = (t) => Math.min(last, Math.max(0, Math.floor(t / frameMs + 1e-6)));
328
+ const weights = /* @__PURE__ */ new Map();
329
+ for (const f of raw) {
330
+ if (!Number.isFinite(f.time) || !Number.isFinite(f.weight) || f.weight <= 0) continue;
331
+ const i = frameOf(f.time);
332
+ weights.set(i, (weights.get(i) ?? 0) + f.weight);
333
+ }
334
+ if (!weights.size) weights.set(Number.isFinite(request.time) ? frameOf(request.time) : 0, 1);
335
+ let frames = [...weights.entries()].sort((a, b) => a[0] - b[0]);
336
+ if (frames.length > TIMING_MAX_FRAMES) {
337
+ const stride = frames.length / TIMING_MAX_FRAMES;
338
+ frames = Array.from({ length: TIMING_MAX_FRAMES }, (_, k) => frames[Math.floor(k * stride)]);
339
+ }
340
+ const total = frames.reduce((sum, [, w]) => sum + w, 0);
341
+ return { frames: frames.map(([i, w]) => ({ time: i * 1e3 / fps, weight: w / total })), blend };
342
+ }
343
+ function timingSampleProblems(sample, request) {
344
+ if (!sample || typeof sample !== "object") return ["the sample is not an object { frames, blend }"];
345
+ const { frames, blend } = sample;
346
+ const problems = [];
347
+ if (!TIMING_BLENDS.includes(blend)) problems.push(`blend "${String(blend)}" is not one of ${TIMING_BLENDS.join(", ")}`);
348
+ if (!Array.isArray(frames) || !frames.length) return [...problems, "frames must be a list of at least one { time, weight }"];
349
+ if (frames.length > TIMING_MAX_FRAMES) problems.push(`it has ${frames.length} frames; at most ${TIMING_MAX_FRAMES} are allowed`);
350
+ const fps = request.fps ?? TIMING_DEFAULT_FPS;
351
+ const last = lastFrameOf(request.footage, fps);
352
+ let total = 0;
353
+ let previous = -Infinity;
354
+ for (const f of frames) {
355
+ if (!f || !Number.isFinite(f.time) || !Number.isFinite(f.weight)) {
356
+ problems.push(`frame ${JSON.stringify(f)} needs a finite time and weight`);
357
+ continue;
358
+ }
359
+ const index = f.time * fps / 1e3;
360
+ if (Math.abs(index - Math.round(index)) > 1e-6) problems.push(`frame time ${f.time} is not the start of a frame at ${fps} fps (index \xD7 ${+(1e3 / fps).toFixed(6)} ms)`);
361
+ else if (Math.round(index) < 0 || Math.round(index) > last) problems.push(`frame time ${f.time} is outside the footage (frames 0 to ${last}, the last at ${+(last * 1e3 / fps).toFixed(6)} ms)`);
362
+ if (f.time <= previous) problems.push(`frame times must be distinct and earliest first; ${f.time} follows ${previous}`);
363
+ previous = f.time;
364
+ if (!(f.weight > 0)) problems.push(`frame at ${f.time} has weight ${f.weight}; weights are > 0`);
365
+ total += f.weight;
366
+ }
367
+ if (Math.abs(total - 1) > 1e-6) problems.push(`weights add up to ${total}, not 1`);
368
+ return problems;
369
+ }
370
+ function blendTimingFrames(frames, blend, out) {
371
+ if (!frames.length) throw new EffectUnsupportedError("There are no frames to blend; a timing sample has at least one.");
372
+ const { width, height } = frames[0].pixels;
373
+ for (const f of frames) {
374
+ if (f.pixels.width !== width || f.pixels.height !== height) throw new EffectUnsupportedError(`A ${f.pixels.width}\xD7${f.pixels.height} frame cannot be blended with ${width}\xD7${height} frames.`);
375
+ }
376
+ const target = out ?? createEffectPixels(width, height);
377
+ if (target.width !== width || target.height !== height) throw new EffectUnsupportedError(`The output is ${target.width}\xD7${target.height}; the frames are ${width}\xD7${height}.`);
378
+ const o = target.data;
379
+ if (frames.length === 1) {
380
+ o.set(frames[0].pixels.data);
381
+ return target;
382
+ }
383
+ const s = new Float64Array(o.length);
384
+ if (blend === "lighten") {
385
+ const heaviest = Math.max(...frames.map((f) => f.weight));
386
+ for (const f of frames) {
387
+ const k = heaviest > 0 ? f.weight / heaviest : 1;
388
+ const p = f.pixels.data;
389
+ for (let i = 0; i < s.length; i += 4) {
390
+ const a = p[i + 3] / 255 * k;
391
+ for (let c = 0; c < 3; c++) if (p[i + c] * a > s[i + c]) s[i + c] = p[i + c] * a;
392
+ if (a > s[i + 3]) s[i + 3] = a;
393
+ }
394
+ }
395
+ for (let i = 0; i < s.length; i += 4) {
396
+ for (let c = 0; c < 3; c++) o[i + c] = Math.round(s[i + c]);
397
+ o[i + 3] = Math.round(s[i + 3] * 255);
398
+ }
399
+ return target;
400
+ }
401
+ let total = 0;
402
+ for (const f of frames) {
403
+ total += f.weight;
404
+ const p = f.pixels.data;
405
+ for (let i = 0; i < s.length; i += 4) {
406
+ const a = p[i + 3] * f.weight;
407
+ for (let c = 0; c < 3; c++) s[i + c] += p[i + c] * a;
408
+ s[i + 3] += a;
409
+ }
410
+ }
411
+ for (let i = 0; i < s.length; i += 4) {
412
+ const a = s[i + 3];
413
+ for (let c = 0; c < 3; c++) o[i + c] = a > 0 ? Math.round(s[i + c] / a) : 0;
414
+ o[i + 3] = total > 0 ? Math.round(a / total) : 0;
415
+ }
416
+ return target;
417
+ }
302
418
 
303
419
  // src/effects/tools.ts
304
420
  var ANNOTATIONS = {
@@ -392,12 +508,12 @@ function createEffectToolProvider(host) {
392
508
  {
393
509
  name: "search_effects",
394
510
  title: "Search effects",
395
- description: `Searches the effects provider's catalog. Every word of query must appear in an effect's id, name, category, tags or description; filter by kind (shader, vector, motion, camera), category, use (${EFFECT_USES.join(", ")}) or tags (effects carrying every one), and accepts ["path"] for effects that draw along a path you give. For this editor, apply_effect takes uses ${host.applyUses.join(" or ")}. Returns { provider, effects: [{ id, name, kind, category, uses, description, tags, transparent, loop, duration (ms), camera: { role, durationMs, cut } for camera moves, accepts }], total, tags: [{ tag, count }] }: tags lists the tags of every match, the most used first, so a broad search (or none) shows the tags to narrow by. A tag the catalog does not have comes back in unknownTags with the nearest ones. With no provider registered it returns available: false and the reason. Call describe_effect next for an effect's controls.`,
511
+ description: `Searches the effects provider's catalog. Every word of query must appear in an effect's id, name, category, tags or description; filter by kind (shader, vector, motion, camera, timing), category, use (${EFFECT_USES.join(", ")}) or tags (effects carrying every one), and accepts ["path"] for effects that draw along a path you give. For this editor, apply_effect takes uses ${host.applyUses.join(" or ")}. Returns { provider, effects: [{ id, name, kind, category, uses, description, tags, transparent, loop, duration (ms), camera: { role, durationMs, cut } for camera moves, accepts }] (timing effects, kind timing, retime video footage and have no duration: their length depends on the footage), total, tags: [{ tag, count }] }: tags lists the tags of every match, the most used first, so a broad search (or none) shows the tags to narrow by. A tag the catalog does not have comes back in unknownTags with the nearest ones. With no provider registered it returns available: false and the reason. Call describe_effect next for an effect's controls.`,
396
512
  inputSchema: object({
397
513
  query: { type: "string", description: "Words to match (default: every effect)." },
398
- kind: { type: "string", enum: [...EFFECT_KINDS], description: "shader (pixels), vector (animated SVG), motion (animates vector art) or camera (moves a scene's camera; no pixels)." },
514
+ kind: { type: "string", enum: [...EFFECT_KINDS], description: "shader (pixels), vector (animated SVG), motion (animates vector art), camera (moves a scene's camera; no pixels) or timing (retimes video footage: slow motion, reverse, freeze, long exposure; no pixels of its own)." },
399
515
  category: { type: "string", minLength: 1, description: `The provider's category, e.g. "gradient", "light", "transition".` },
400
- use: { type: "string", enum: [...EFFECT_USES], description: "What the effect is for: filter changes a picture, transition blends two, generator draws a background, overlay draws over content, title draws a title, motion animates vector art, interactive reacts to input, camera moves a scene's camera." },
516
+ use: { type: "string", enum: [...EFFECT_USES], description: "What the effect is for: filter changes a picture, transition blends two, generator draws a background, overlay draws over content, title draws a title, motion animates vector art, interactive reacts to input, camera moves a scene's camera, timing retimes video footage." },
401
517
  tags: { type: "array", items: { type: "string", minLength: 1 }, description: "Only effects carrying every one of these tags, as search_effects lists them (any case; spaces count as hyphens)." },
402
518
  limit: { type: "integer", minimum: 1, maximum: 200, description: "Most effects to return (default 20)." },
403
519
  accepts: { type: "array", items: { type: "string", enum: [...EFFECT_ACCEPTS], description: "path: the effect has a control that draws along a path given at run time." }, description: "Only effects that take every one of these (path: effects that draw along a path you give; describe_effect names the controls in pathControls)." }
@@ -557,6 +673,10 @@ export {
557
673
  CAMERA_MOVE_ROLES,
558
674
  EFFECT_ACCEPTS,
559
675
  EFFECT_PATH_FORMAT,
676
+ TIMING_DEFAULT_FPS,
677
+ TIMING_MAX_FRAMES,
678
+ TIMING_MAX_FPS,
679
+ TIMING_BLENDS,
560
680
  EFFECT_SUBJECT_KINDS,
561
681
  EFFECT_LAYER_ROLES,
562
682
  EFFECT_TRANSITION_MS,
@@ -597,5 +717,10 @@ export {
597
717
  resolveCameraMoveSpec,
598
718
  cameraViewOfBox,
599
719
  cameraBoxOfView,
720
+ checkTimingFootage,
721
+ checkTimingRequest,
722
+ normaliseTimingSample,
723
+ timingSampleProblems,
724
+ blendTimingFrames,
600
725
  createEffectToolProvider
601
726
  };
@@ -7,7 +7,7 @@ import {
7
7
  EffectsSlot,
8
8
  createEffectPixels,
9
9
  effectValueErrors
10
- } from "./chunk-JA57JIT5.js";
10
+ } from "./chunk-MV7CYFMJ.js";
11
11
  import {
12
12
  validateJsonSchema
13
13
  } from "./chunk-V2ZCJRDM.js";
@@ -22,4 +22,5 @@ export interface EffectConformanceOptions {
22
22
  /** Also check sequence templates when supportsTemplates is true (default true). */
23
23
  templates?: boolean;
24
24
  }
25
+ /** Runs the contract checks against `provider` and returns every issue found (empty when it conforms). */
25
26
  export declare function checkEffectProvider(provider: EffectProvider, options?: EffectConformanceOptions): Promise<EffectConformanceIssue[]>;
@@ -22,4 +22,5 @@ export interface EffectConformanceOptions {
22
22
  /** Also check sequence templates when supportsTemplates is true (default true). */
23
23
  templates?: boolean;
24
24
  }
25
+ /** Runs the contract checks against `provider` and returns every issue found (empty when it conforms). */
25
26
  export declare function checkEffectProvider(provider: EffectProvider, options?: EffectConformanceOptions): Promise<EffectConformanceIssue[]>;
@@ -17,6 +17,10 @@
17
17
  * With `camera: true` it also has camera moves (no pixels): `glide` and `whip` (transitions; the
18
18
  * whip cuts halfway) and `drift` (a hold that zooms in slowly).
19
19
  *
20
+ * With `timing: true` it also has timing effects (no pixels; they retime video footage): `speed`
21
+ * (slow or fast, blending frames), `freeze` (holds one frame, then plays on) and `echo` (fading
22
+ * copies of the frames before, averaged or lightened).
23
+ *
20
24
  * With `templates: true` it also has two sequence templates (see `fake-templates.ts`):
21
25
  * `fake.title-sequence` and `fake.picture-reveal`.
22
26
  */
@@ -30,8 +34,10 @@ export interface FakeEffectProviderOptions {
30
34
  authoring?: boolean;
31
35
  /** Adds the camera moves and `cameraView` / `sampleCameraMove` (default false). */
32
36
  camera?: boolean;
37
+ /** Adds the timing effects and `timingLength` / `timingSample` (default false). */
38
+ timing?: boolean;
33
39
  /** Adds two sequence templates and `listTemplates` / `describeTemplate` / `instantiateTemplate` (default false). */
34
40
  templates?: boolean;
35
41
  }
36
- /** A deterministic CPU effects provider with seven effects, and camera moves and templates on request (see the module comment). */
42
+ /** A deterministic CPU effects provider with nine effects, and camera moves, timing effects and templates on request (see the module comment). */
37
43
  export declare function createFakeEffectProvider(options?: FakeEffectProviderOptions): EffectProvider;
@@ -17,6 +17,10 @@
17
17
  * With `camera: true` it also has camera moves (no pixels): `glide` and `whip` (transitions; the
18
18
  * whip cuts halfway) and `drift` (a hold that zooms in slowly).
19
19
  *
20
+ * With `timing: true` it also has timing effects (no pixels; they retime video footage): `speed`
21
+ * (slow or fast, blending frames), `freeze` (holds one frame, then plays on) and `echo` (fading
22
+ * copies of the frames before, averaged or lightened).
23
+ *
20
24
  * With `templates: true` it also has two sequence templates (see `fake-templates.ts`):
21
25
  * `fake.title-sequence` and `fake.picture-reveal`.
22
26
  */
@@ -30,8 +34,10 @@ export interface FakeEffectProviderOptions {
30
34
  authoring?: boolean;
31
35
  /** Adds the camera moves and `cameraView` / `sampleCameraMove` (default false). */
32
36
  camera?: boolean;
37
+ /** Adds the timing effects and `timingLength` / `timingSample` (default false). */
38
+ timing?: boolean;
33
39
  /** Adds two sequence templates and `listTemplates` / `describeTemplate` / `instantiateTemplate` (default false). */
34
40
  templates?: boolean;
35
41
  }
36
- /** A deterministic CPU effects provider with seven effects, and camera moves and templates on request (see the module comment). */
42
+ /** A deterministic CPU effects provider with nine effects, and camera moves, timing effects and templates on request (see the module comment). */
37
43
  export declare function createFakeEffectProvider(options?: FakeEffectProviderOptions): EffectProvider;
@@ -3,7 +3,7 @@
3
3
  * and a provider slot editors hold.
4
4
  */
5
5
  import { type SchemaValidationError } from '../tools/schema.cjs';
6
- import type { CameraMoveRole, CameraMoveSpec, CameraMoveSample, CameraMoveSampleRequest, CameraStage, CameraView, EffectDescription, EffectImageSource, EffectPixels, EffectProvider, EffectSearchOptions, EffectSummary, EffectUse, EffectValues } from './types.cjs';
6
+ import type { CameraMoveRole, CameraMoveSpec, CameraMoveSample, CameraMoveSampleRequest, CameraStage, CameraView, EffectDescription, EffectImageSource, EffectPixels, EffectProvider, EffectSearchOptions, EffectSummary, EffectUse, EffectValues, TimingBlend, TimingFrame, TimingSample, TimingSampleRequest } from './types.cjs';
7
7
  /** Problems with `values` against an effect's control schema (unknown controls, types, ranges). Partial values are fine. */
8
8
  export declare function effectValueErrors(description: Pick<EffectDescription, 'controls'>, values: unknown): SchemaValidationError[];
9
9
  /**
@@ -13,9 +13,9 @@ export declare function effectValueErrors(description: Pick<EffectDescription, '
13
13
  */
14
14
  export declare function resolveEffectValues(description: Pick<EffectDescription, 'id' | 'controls' | 'defaults'>, values?: EffectValues): Record<string, EffectValues[string]>;
15
15
  /**
16
- * Whether an editor offering `uses` should offer `e` (in a picker or an `effect` enum). Motion
17
- * and camera effects draw no picture of their own, so they are offered only where `uses` names
18
- * them (`motion`, `camera`); with no `uses`, every other effect is.
16
+ * Whether an editor offering `uses` should offer `e` (in a picker or an `effect` enum). Motion,
17
+ * camera and timing effects draw no picture of their own, so they are offered only where `uses`
18
+ * names them (`motion`, `camera`, `timing`); with no `uses`, every other effect is.
19
19
  */
20
20
  export declare function effectOffered(e: Pick<EffectSummary, 'kind' | 'uses'>, uses?: readonly EffectUse[]): boolean;
21
21
  /** A tag as tags are compared: trimmed, lower case, spaces and underscores as single hyphens (`"Game Boy"` → `game-boy`). */
@@ -128,3 +128,35 @@ export interface CameraBox {
128
128
  export declare function cameraViewOfBox(box: CameraBox, stage: CameraStage): CameraView;
129
129
  /** The box of the scene a view shows on `stage` (its tilt, turn, roll and blur aside). */
130
130
  export declare function cameraBoxOfView(view: CameraView, stage: CameraStage): CameraBox;
131
+ /** Throws `EffectUnsupportedError` unless `footageMs` is a footage length a timing effect takes (a number of ms > 0). */
132
+ export declare function checkTimingFootage(footageMs: number, id?: string): void;
133
+ /**
134
+ * Checks a timing request (footage, time and fps), throwing `EffectUnsupportedError` with a
135
+ * sentence naming the valid range, and gives its fps (default `TIMING_DEFAULT_FPS`).
136
+ */
137
+ export declare function checkTimingRequest(request: TimingSampleRequest, id?: string): number;
138
+ /**
139
+ * Raw footage frames (any times, any positive weights) as a `TimingSample`: each time held within
140
+ * the footage and moved to the start of its frame, frames on the same footage frame merged, at
141
+ * most `TIMING_MAX_FRAMES` (evenly spaced), weights scaled to add up to 1, earliest first. Frames
142
+ * with a time or weight that is not a finite number, or a weight ≤ 0, are dropped; with none left,
143
+ * the sample is the frame at `request.time`. Providers can build `timingSample` on it.
144
+ */
145
+ export declare function normaliseTimingSample(raw: readonly TimingFrame[], request: TimingSampleRequest, blend?: TimingBlend): TimingSample;
146
+ /**
147
+ * What is wrong with `sample` as the answer to `request` (see `TimingSample`), as sentences; empty
148
+ * when it keeps every rule. `checkEffectProvider` uses it; hosts can check a provider's samples
149
+ * with it.
150
+ */
151
+ export declare function timingSampleProblems(sample: unknown, request: TimingSampleRequest): string[];
152
+ /**
153
+ * Blends footage frames by weight as a timing sample says (see `TimingBlend`): the reference for
154
+ * hosts that blend in their own pipeline, and a ready blend for `EffectPixels`. Reads and writes
155
+ * 8-bit `data` (straight alpha, rounded); `float` is not used. Writes into `out` when given (the
156
+ * frames' size), else a new picture. Throws `EffectUnsupportedError` for no frames or frames of
157
+ * different sizes.
158
+ */
159
+ export declare function blendTimingFrames(frames: readonly {
160
+ pixels: EffectPixels;
161
+ weight: number;
162
+ }[], blend: TimingBlend, out?: EffectPixels): EffectPixels;
@@ -3,7 +3,7 @@
3
3
  * and a provider slot editors hold.
4
4
  */
5
5
  import { type SchemaValidationError } from '../tools/schema.js';
6
- import type { CameraMoveRole, CameraMoveSpec, CameraMoveSample, CameraMoveSampleRequest, CameraStage, CameraView, EffectDescription, EffectImageSource, EffectPixels, EffectProvider, EffectSearchOptions, EffectSummary, EffectUse, EffectValues } from './types.js';
6
+ import type { CameraMoveRole, CameraMoveSpec, CameraMoveSample, CameraMoveSampleRequest, CameraStage, CameraView, EffectDescription, EffectImageSource, EffectPixels, EffectProvider, EffectSearchOptions, EffectSummary, EffectUse, EffectValues, TimingBlend, TimingFrame, TimingSample, TimingSampleRequest } from './types.js';
7
7
  /** Problems with `values` against an effect's control schema (unknown controls, types, ranges). Partial values are fine. */
8
8
  export declare function effectValueErrors(description: Pick<EffectDescription, 'controls'>, values: unknown): SchemaValidationError[];
9
9
  /**
@@ -13,9 +13,9 @@ export declare function effectValueErrors(description: Pick<EffectDescription, '
13
13
  */
14
14
  export declare function resolveEffectValues(description: Pick<EffectDescription, 'id' | 'controls' | 'defaults'>, values?: EffectValues): Record<string, EffectValues[string]>;
15
15
  /**
16
- * Whether an editor offering `uses` should offer `e` (in a picker or an `effect` enum). Motion
17
- * and camera effects draw no picture of their own, so they are offered only where `uses` names
18
- * them (`motion`, `camera`); with no `uses`, every other effect is.
16
+ * Whether an editor offering `uses` should offer `e` (in a picker or an `effect` enum). Motion,
17
+ * camera and timing effects draw no picture of their own, so they are offered only where `uses`
18
+ * names them (`motion`, `camera`, `timing`); with no `uses`, every other effect is.
19
19
  */
20
20
  export declare function effectOffered(e: Pick<EffectSummary, 'kind' | 'uses'>, uses?: readonly EffectUse[]): boolean;
21
21
  /** A tag as tags are compared: trimmed, lower case, spaces and underscores as single hyphens (`"Game Boy"` → `game-boy`). */
@@ -128,3 +128,35 @@ export interface CameraBox {
128
128
  export declare function cameraViewOfBox(box: CameraBox, stage: CameraStage): CameraView;
129
129
  /** The box of the scene a view shows on `stage` (its tilt, turn, roll and blur aside). */
130
130
  export declare function cameraBoxOfView(view: CameraView, stage: CameraStage): CameraBox;
131
+ /** Throws `EffectUnsupportedError` unless `footageMs` is a footage length a timing effect takes (a number of ms > 0). */
132
+ export declare function checkTimingFootage(footageMs: number, id?: string): void;
133
+ /**
134
+ * Checks a timing request (footage, time and fps), throwing `EffectUnsupportedError` with a
135
+ * sentence naming the valid range, and gives its fps (default `TIMING_DEFAULT_FPS`).
136
+ */
137
+ export declare function checkTimingRequest(request: TimingSampleRequest, id?: string): number;
138
+ /**
139
+ * Raw footage frames (any times, any positive weights) as a `TimingSample`: each time held within
140
+ * the footage and moved to the start of its frame, frames on the same footage frame merged, at
141
+ * most `TIMING_MAX_FRAMES` (evenly spaced), weights scaled to add up to 1, earliest first. Frames
142
+ * with a time or weight that is not a finite number, or a weight ≤ 0, are dropped; with none left,
143
+ * the sample is the frame at `request.time`. Providers can build `timingSample` on it.
144
+ */
145
+ export declare function normaliseTimingSample(raw: readonly TimingFrame[], request: TimingSampleRequest, blend?: TimingBlend): TimingSample;
146
+ /**
147
+ * What is wrong with `sample` as the answer to `request` (see `TimingSample`), as sentences; empty
148
+ * when it keeps every rule. `checkEffectProvider` uses it; hosts can check a provider's samples
149
+ * with it.
150
+ */
151
+ export declare function timingSampleProblems(sample: unknown, request: TimingSampleRequest): string[];
152
+ /**
153
+ * Blends footage frames by weight as a timing sample says (see `TimingBlend`): the reference for
154
+ * hosts that blend in their own pipeline, and a ready blend for `EffectPixels`. Reads and writes
155
+ * 8-bit `data` (straight alpha, rounded); `float` is not used. Writes into `out` when given (the
156
+ * frames' size), else a new picture. Throws `EffectUnsupportedError` for no frames or frames of
157
+ * different sizes.
158
+ */
159
+ export declare function blendTimingFrames(frames: readonly {
160
+ pixels: EffectPixels;
161
+ weight: number;
162
+ }[], blend: TimingBlend, out?: EffectPixels): EffectPixels;