@pixerate/editor 0.159.0 → 0.161.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.
Files changed (63) hide show
  1. package/dist/{chunk-VKF3PWKR.cjs → chunk-3OSXHLIT.cjs} +259 -28
  2. package/dist/chunk-6DM3PUQD.cjs +767 -0
  3. package/dist/{chunk-IQ6KKXOU.js → chunk-YN4AYYBJ.js} +243 -12
  4. package/dist/chunk-ZGOHKS3X.js +767 -0
  5. package/dist/effects/index.cjs +16 -767
  6. package/dist/effects/index.js +5 -756
  7. package/dist/image-editor/controller.d.cts +7 -0
  8. package/dist/image-editor/controller.d.ts +7 -0
  9. package/dist/image-editor/index.cjs +49 -10
  10. package/dist/image-editor/index.js +48 -9
  11. package/dist/image-editor/layers/transform-ui.d.cts +14 -6
  12. package/dist/image-editor/layers/transform-ui.d.ts +14 -6
  13. package/dist/paint-editor/index.cjs +2 -2
  14. package/dist/paint-editor/index.js +1 -1
  15. package/dist/video-editor/audio.d.cts +9 -0
  16. package/dist/video-editor/audio.d.ts +9 -0
  17. package/dist/video-editor/check.d.cts +1 -1
  18. package/dist/video-editor/check.d.ts +1 -1
  19. package/dist/video-editor/controller.d.cts +47 -0
  20. package/dist/video-editor/controller.d.ts +47 -0
  21. package/dist/video-editor/dialogue-tools.d.cts +19 -0
  22. package/dist/video-editor/dialogue-tools.d.ts +19 -0
  23. package/dist/video-editor/effect-authoring.d.cts +54 -0
  24. package/dist/video-editor/effect-authoring.d.ts +54 -0
  25. package/dist/video-editor/evaluate.d.cts +11 -0
  26. package/dist/video-editor/evaluate.d.ts +11 -0
  27. package/dist/video-editor/export.d.cts +7 -1
  28. package/dist/video-editor/export.d.ts +7 -1
  29. package/dist/video-editor/index.cjs +1853 -528
  30. package/dist/video-editor/index.d.cts +7 -0
  31. package/dist/video-editor/index.d.ts +7 -0
  32. package/dist/video-editor/index.js +1512 -187
  33. package/dist/video-editor/lut.d.cts +12 -2
  34. package/dist/video-editor/lut.d.ts +12 -2
  35. package/dist/video-editor/mask.d.cts +48 -0
  36. package/dist/video-editor/mask.d.ts +48 -0
  37. package/dist/video-editor/mix.d.cts +107 -0
  38. package/dist/video-editor/mix.d.ts +107 -0
  39. package/dist/video-editor/music-tools.d.cts +19 -0
  40. package/dist/video-editor/music-tools.d.ts +19 -0
  41. package/dist/video-editor/parse.d.cts +3 -1
  42. package/dist/video-editor/parse.d.ts +3 -1
  43. package/dist/video-editor/plan.d.cts +75 -0
  44. package/dist/video-editor/plan.d.ts +75 -0
  45. package/dist/video-editor/render.d.cts +1 -1
  46. package/dist/video-editor/render.d.ts +1 -1
  47. package/dist/video-editor/timeline.d.cts +2 -0
  48. package/dist/video-editor/timeline.d.ts +2 -0
  49. package/dist/video-editor/tools.d.cts +90 -0
  50. package/dist/video-editor/tools.d.ts +90 -0
  51. package/dist/video-editor/transcript-controller.d.cts +106 -0
  52. package/dist/video-editor/transcript-controller.d.ts +106 -0
  53. package/dist/video-editor/transcript.d.cts +40 -0
  54. package/dist/video-editor/transcript.d.ts +40 -0
  55. package/dist/video-editor/types.d.cts +62 -2
  56. package/dist/video-editor/types.d.ts +62 -2
  57. package/dist/video-editor/ui.d.cts +21 -1
  58. package/dist/video-editor/ui.d.ts +21 -1
  59. package/package.json +1 -1
  60. package/skills/README.md +2 -0
  61. package/skills/pixerate-bgm-alignment/SKILL.md +82 -0
  62. package/skills/pixerate-talking-head-edit/SKILL.md +80 -0
  63. package/skills/pixerate-video-editor/SKILL.md +40 -2
@@ -28,11 +28,21 @@ export declare function videoLutToCube(lut: VideoLut): string;
28
28
  * from the four corners of the one it falls in. Greys stay on the diagonal, unlike trilinear.
29
29
  */
30
30
  export declare function applyVideoLut(lut: VideoLut, rgb: readonly [number, number, number], out?: [number, number, number]): [number, number, number];
31
- /** Runs RGBA pixels (0–255, as `ImageData` has them) through a LUT in place, mixed with the originals by `strength` (0–1). Transparent pixels are left alone. */
31
+ /**
32
+ * How much a colour (0–1 sRGB) looks like skin, 0–1: its chroma (BT.601 Cb / Cr) against the
33
+ * skin cluster, which holds for every complexion because skin differs mostly in lightness, not in
34
+ * hue. Full weight inside the cluster, falling smoothly to 0 just outside it, and faded out near
35
+ * black and white, where chroma means little. Used by `preserveSkinTones`.
36
+ */
37
+ export declare function videoSkinToneWeight(r: number, g: number, b: number): number;
38
+ /**
39
+ * Runs RGBA pixels (0–255, e.g. `ImageData.data`) through a LUT in place, mixed by `strength`.
40
+ * With `preserveSkinTones` (0–1) the mix is held back by that much on skin (see `videoSkinToneWeight`).
41
+ */
32
42
  export declare function applyVideoLutToPixels(lut: VideoLut, data: {
33
43
  length: number;
34
44
  [i: number]: number;
35
- }, strength?: number): void;
45
+ }, strength?: number, preserveSkinTones?: number): void;
36
46
  /**
37
47
  * An SVG approximation of a LUT for SVG frames, which have no 3D tables: per-channel curves taken
38
48
  * along the LUT's grey axis (`feComponentTransfer` tables), then the colour matrix that best fits
@@ -28,11 +28,21 @@ export declare function videoLutToCube(lut: VideoLut): string;
28
28
  * from the four corners of the one it falls in. Greys stay on the diagonal, unlike trilinear.
29
29
  */
30
30
  export declare function applyVideoLut(lut: VideoLut, rgb: readonly [number, number, number], out?: [number, number, number]): [number, number, number];
31
- /** Runs RGBA pixels (0–255, as `ImageData` has them) through a LUT in place, mixed with the originals by `strength` (0–1). Transparent pixels are left alone. */
31
+ /**
32
+ * How much a colour (0–1 sRGB) looks like skin, 0–1: its chroma (BT.601 Cb / Cr) against the
33
+ * skin cluster, which holds for every complexion because skin differs mostly in lightness, not in
34
+ * hue. Full weight inside the cluster, falling smoothly to 0 just outside it, and faded out near
35
+ * black and white, where chroma means little. Used by `preserveSkinTones`.
36
+ */
37
+ export declare function videoSkinToneWeight(r: number, g: number, b: number): number;
38
+ /**
39
+ * Runs RGBA pixels (0–255, e.g. `ImageData.data`) through a LUT in place, mixed by `strength`.
40
+ * With `preserveSkinTones` (0–1) the mix is held back by that much on skin (see `videoSkinToneWeight`).
41
+ */
32
42
  export declare function applyVideoLutToPixels(lut: VideoLut, data: {
33
43
  length: number;
34
44
  [i: number]: number;
35
- }, strength?: number): void;
45
+ }, strength?: number, preserveSkinTones?: number): void;
36
46
  /**
37
47
  * An SVG approximation of a LUT for SVG frames, which have no 3D tables: per-channel curves taken
38
48
  * along the LUT's grey axis (`feComponentTransfer` tables), then the colour matrix that best fits
@@ -0,0 +1,48 @@
1
+ import type { VideoClipMask } from './types.cjs';
2
+ /**
3
+ * Clip masks as plain geometry: every shape becomes a polygon in frame pixels (curves as short
4
+ * straight segments), so the canvas preview, SVG frames and agents all cut the same shape. The
5
+ * polygon is in the layer's unrotated frame space: renderers turn it with the layer.
6
+ */
7
+ /** A rect in frame pixels (the layer's placed picture). */
8
+ interface Rect {
9
+ x: number;
10
+ y: number;
11
+ width: number;
12
+ height: number;
13
+ }
14
+ /** A mask resolved for one layer: its outline in frame pixels, soft edge and side. */
15
+ export interface VideoLayerMask {
16
+ /** The outline, `[x, y]` frame pixels before the layer's rotation. */
17
+ points: [number, number][];
18
+ /** Soft edge width in frame pixels (0 = hard). */
19
+ feather: number;
20
+ /** Show the outside instead of the inside. */
21
+ inverted: boolean;
22
+ }
23
+ /** The defaults a mask's missing fields take. */
24
+ export declare const VIDEO_MASK_DEFAULTS: {
25
+ readonly x: 0.5;
26
+ readonly y: 0.5;
27
+ readonly width: 0.5;
28
+ readonly height: 0.5;
29
+ readonly rotation: 0;
30
+ readonly feather: 0;
31
+ readonly cornerRadius: 0;
32
+ };
33
+ /** A mask with its defaults filled in and values clamped (corner radius 0–1, sizes and feather at least 0). */
34
+ export declare function normalizeVideoClipMask(mask: VideoClipMask): Required<Omit<VideoClipMask, 'points' | 'inverted'>> & Pick<VideoClipMask, 'points'> & {
35
+ inverted: boolean;
36
+ };
37
+ /** The mask's outline in frame pixels for a layer placed at `rect` (before the layer's own rotation). */
38
+ export declare function videoMaskPolygon(mask: VideoClipMask, rect: Rect): [number, number][];
39
+ /** A mask resolved for a layer placed at `rect`. */
40
+ export declare function videoLayerMask(mask: VideoClipMask, rect: Rect): VideoLayerMask;
41
+ /** An SVG path (`d`) of a resolved mask's outline. */
42
+ export declare function videoMaskSvgPath(mask: VideoLayerMask): string;
43
+ /**
44
+ * How much of a point (frame pixels) a resolved mask shows, 0 or 1 (no feather): an even-odd test
45
+ * against its outline. For agents and tests reading what a mask keeps.
46
+ */
47
+ export declare function videoMaskContains(mask: VideoLayerMask, x: number, y: number): boolean;
48
+ export {};
@@ -0,0 +1,48 @@
1
+ import type { VideoClipMask } from './types.js';
2
+ /**
3
+ * Clip masks as plain geometry: every shape becomes a polygon in frame pixels (curves as short
4
+ * straight segments), so the canvas preview, SVG frames and agents all cut the same shape. The
5
+ * polygon is in the layer's unrotated frame space: renderers turn it with the layer.
6
+ */
7
+ /** A rect in frame pixels (the layer's placed picture). */
8
+ interface Rect {
9
+ x: number;
10
+ y: number;
11
+ width: number;
12
+ height: number;
13
+ }
14
+ /** A mask resolved for one layer: its outline in frame pixels, soft edge and side. */
15
+ export interface VideoLayerMask {
16
+ /** The outline, `[x, y]` frame pixels before the layer's rotation. */
17
+ points: [number, number][];
18
+ /** Soft edge width in frame pixels (0 = hard). */
19
+ feather: number;
20
+ /** Show the outside instead of the inside. */
21
+ inverted: boolean;
22
+ }
23
+ /** The defaults a mask's missing fields take. */
24
+ export declare const VIDEO_MASK_DEFAULTS: {
25
+ readonly x: 0.5;
26
+ readonly y: 0.5;
27
+ readonly width: 0.5;
28
+ readonly height: 0.5;
29
+ readonly rotation: 0;
30
+ readonly feather: 0;
31
+ readonly cornerRadius: 0;
32
+ };
33
+ /** A mask with its defaults filled in and values clamped (corner radius 0–1, sizes and feather at least 0). */
34
+ export declare function normalizeVideoClipMask(mask: VideoClipMask): Required<Omit<VideoClipMask, 'points' | 'inverted'>> & Pick<VideoClipMask, 'points'> & {
35
+ inverted: boolean;
36
+ };
37
+ /** The mask's outline in frame pixels for a layer placed at `rect` (before the layer's own rotation). */
38
+ export declare function videoMaskPolygon(mask: VideoClipMask, rect: Rect): [number, number][];
39
+ /** A mask resolved for a layer placed at `rect`. */
40
+ export declare function videoLayerMask(mask: VideoClipMask, rect: Rect): VideoLayerMask;
41
+ /** An SVG path (`d`) of a resolved mask's outline. */
42
+ export declare function videoMaskSvgPath(mask: VideoLayerMask): string;
43
+ /**
44
+ * How much of a point (frame pixels) a resolved mask shows, 0 or 1 (no feather): an even-odd test
45
+ * against its outline. For agents and tests reading what a mask keeps.
46
+ */
47
+ export declare function videoMaskContains(mask: VideoLayerMask, x: number, y: number): boolean;
48
+ export {};
@@ -0,0 +1,107 @@
1
+ import { type VideoAudioPeaks } from './audio.cjs';
2
+ import type { VideoKey } from './keyframes.cjs';
3
+ import { type VideoTimelineWord } from './transcript.cjs';
4
+ import type { VideoClip } from './types.cjs';
5
+ /**
6
+ * The sound mix as data: loudness measured the way streaming services and broadcasters measure it
7
+ * (ITU-R BS.1770, gated, in LUFS), the gain that brings a clip to a target, ducking keyframes that
8
+ * dip music under speech, and the beats of a music track. Pure functions over samples, peaks and
9
+ * ranges, so an agent and the inspector get the same numbers, and tests need no decoder.
10
+ */
11
+ /** Length of one loudness hop (ms): a gating block is four hops (400 ms) moving one hop at a time. */
12
+ export declare const VIDEO_LOUDNESS_HOP = 100;
13
+ /**
14
+ * The K-weighted mean square of decoded audio per 100 ms hop, channels summed (each weighted 1, as
15
+ * for left, right and centre). What `VideoAudioPeaks.loudness` holds.
16
+ */
17
+ export declare function videoLoudnessEnergy(channels: readonly Float32Array[], sampleRate: number): {
18
+ hop: number;
19
+ energy: Float32Array;
20
+ };
21
+ /**
22
+ * Integrated loudness (LUFS) of hop energies between two source times (ms): 400 ms blocks every
23
+ * 100 ms, gated at -70 LUFS and then 10 LU below the ungated level, as BS.1770-4 does. A stretch
24
+ * shorter than a block is measured as one block. `null` when everything is gated out (silence).
25
+ */
26
+ export declare function videoLoudnessOf(loudness: {
27
+ hop: number;
28
+ energy: Float32Array;
29
+ }, from?: number, to?: number): number | null;
30
+ /** Integrated loudness (LUFS) of decoded audio. `null` for silence. */
31
+ export declare function measureVideoLoudness(channels: readonly Float32Array[], sampleRate: number): number | null;
32
+ /** Integrated loudness (LUFS) of a stretch of an asset's sound from its peaks, `null` for silence; throws for peaks without loudness. */
33
+ export declare function videoAudioLoudness(peaks: VideoAudioPeaks, from?: number, to?: number): number | null;
34
+ export interface VideoLoudnessGainOptions {
35
+ /** Most gain allowed (linear, default 4, the highest clip volume). */
36
+ maxGain?: number;
37
+ /** The sound's sample peak (dBFS); with it the gain is held so the peak stays under `ceiling`. */
38
+ peakDb?: number;
39
+ /** Highest peak allowed after the gain (dBFS, default -1). */
40
+ ceiling?: number;
41
+ }
42
+ /**
43
+ * The linear gain that brings a sound measured at `measured` LUFS to `target` LUFS (default -14,
44
+ * what streaming services play at), capped by `maxGain` and by the peak ceiling when `peakDb` is
45
+ * known. `limitedBy` says when a cap kept it short of the target.
46
+ */
47
+ export declare function videoLoudnessGain(measured: number, target?: number, options?: VideoLoudnessGainOptions): {
48
+ gain: number;
49
+ gainDb: number;
50
+ limitedBy?: 'max-gain' | 'peak';
51
+ };
52
+ /** Timeline stretches where words are spoken, merged across gaps shorter than `mergeGap` ms (default 500). */
53
+ export declare function videoSpeechRanges(words: readonly Pick<VideoTimelineWord, 'start' | 'end'>[], mergeGap?: number): {
54
+ from: number;
55
+ to: number;
56
+ }[];
57
+ export interface VideoDuckingOptions {
58
+ /** How far to dip under speech, in dB (default -14; -12 to -18 is usual). */
59
+ duckDb?: number;
60
+ /** How long the dip takes to come in before speech starts (ms, default 200). */
61
+ attack?: number;
62
+ /** How long the music takes to come back after speech ends (ms, default 400). */
63
+ release?: number;
64
+ /** Speech gaps shorter than this stay ducked (ms, default 600, never less than attack + release). */
65
+ mergeGap?: number;
66
+ /** The level the music sits at between dips (linear gain, default the clip's own volume or 1). */
67
+ base?: number;
68
+ }
69
+ /**
70
+ * Volume keyframes (clip time) that dip a music clip under speech: for each stretch of speech that
71
+ * overlaps it, the level eases down over `attack` ms before the speech, holds at `duckDb` and eases
72
+ * back over `release` ms after it. Stretches closer than the merge gap stay ducked between them.
73
+ * `[]` when no speech overlaps the clip. Keys are on whole ms and in time order.
74
+ */
75
+ export declare function videoDuckingKeys(clip: Pick<VideoClip, 'start' | 'duration'>, speech: readonly {
76
+ from: number;
77
+ to: number;
78
+ }[], options?: VideoDuckingOptions): VideoKey[];
79
+ export interface VideoBeatOptions {
80
+ /** Resolution of the onset scan (ms, default 10). */
81
+ resolution?: number;
82
+ /** Closest two onsets may be (ms, default 250). */
83
+ minInterval?: number;
84
+ /** How far above the local average an onset must rise, in standard deviations (default 1.5). */
85
+ sensitivity?: number;
86
+ /** Slowest and fastest tempo to consider (BPM, default 60 and 200). */
87
+ minBpm?: number;
88
+ maxBpm?: number;
89
+ }
90
+ export interface VideoBeats {
91
+ /** Times of sharp rises in level (ms, in the clock asked in): hits, plucks, kicks. */
92
+ onsets: number[];
93
+ /** The tempo the onsets repeat at, or `null` when they do not repeat. */
94
+ bpm: number | null;
95
+ /** A steady beat grid at `bpm`, through the strongest onsets (ms); empty without a tempo. */
96
+ beats: number[];
97
+ }
98
+ /**
99
+ * The beats of a stretch of sound (source ms) from its peaks: an onset envelope (rises of the peak
100
+ * level), onsets where it rises well above its local average, the tempo from the envelope's
101
+ * autocorrelation and a beat grid at that tempo, phased to fit the onsets best. Works on peaks, so
102
+ * it is quick and needs no decoding beyond what waveforms already have; it finds the pulse of
103
+ * percussive music well and of ambient music poorly.
104
+ */
105
+ export declare function findVideoAudioBeats(peaks: VideoAudioPeaks, from: number, to: number, options?: VideoBeatOptions): VideoBeats;
106
+ /** The nearest time in a sorted list to `time`, or `null` when none is within `tolerance` ms. */
107
+ export declare function nearestVideoBeat(beats: readonly number[], time: number, tolerance?: number): number | null;
@@ -0,0 +1,107 @@
1
+ import { type VideoAudioPeaks } from './audio.js';
2
+ import type { VideoKey } from './keyframes.js';
3
+ import { type VideoTimelineWord } from './transcript.js';
4
+ import type { VideoClip } from './types.js';
5
+ /**
6
+ * The sound mix as data: loudness measured the way streaming services and broadcasters measure it
7
+ * (ITU-R BS.1770, gated, in LUFS), the gain that brings a clip to a target, ducking keyframes that
8
+ * dip music under speech, and the beats of a music track. Pure functions over samples, peaks and
9
+ * ranges, so an agent and the inspector get the same numbers, and tests need no decoder.
10
+ */
11
+ /** Length of one loudness hop (ms): a gating block is four hops (400 ms) moving one hop at a time. */
12
+ export declare const VIDEO_LOUDNESS_HOP = 100;
13
+ /**
14
+ * The K-weighted mean square of decoded audio per 100 ms hop, channels summed (each weighted 1, as
15
+ * for left, right and centre). What `VideoAudioPeaks.loudness` holds.
16
+ */
17
+ export declare function videoLoudnessEnergy(channels: readonly Float32Array[], sampleRate: number): {
18
+ hop: number;
19
+ energy: Float32Array;
20
+ };
21
+ /**
22
+ * Integrated loudness (LUFS) of hop energies between two source times (ms): 400 ms blocks every
23
+ * 100 ms, gated at -70 LUFS and then 10 LU below the ungated level, as BS.1770-4 does. A stretch
24
+ * shorter than a block is measured as one block. `null` when everything is gated out (silence).
25
+ */
26
+ export declare function videoLoudnessOf(loudness: {
27
+ hop: number;
28
+ energy: Float32Array;
29
+ }, from?: number, to?: number): number | null;
30
+ /** Integrated loudness (LUFS) of decoded audio. `null` for silence. */
31
+ export declare function measureVideoLoudness(channels: readonly Float32Array[], sampleRate: number): number | null;
32
+ /** Integrated loudness (LUFS) of a stretch of an asset's sound from its peaks, `null` for silence; throws for peaks without loudness. */
33
+ export declare function videoAudioLoudness(peaks: VideoAudioPeaks, from?: number, to?: number): number | null;
34
+ export interface VideoLoudnessGainOptions {
35
+ /** Most gain allowed (linear, default 4, the highest clip volume). */
36
+ maxGain?: number;
37
+ /** The sound's sample peak (dBFS); with it the gain is held so the peak stays under `ceiling`. */
38
+ peakDb?: number;
39
+ /** Highest peak allowed after the gain (dBFS, default -1). */
40
+ ceiling?: number;
41
+ }
42
+ /**
43
+ * The linear gain that brings a sound measured at `measured` LUFS to `target` LUFS (default -14,
44
+ * what streaming services play at), capped by `maxGain` and by the peak ceiling when `peakDb` is
45
+ * known. `limitedBy` says when a cap kept it short of the target.
46
+ */
47
+ export declare function videoLoudnessGain(measured: number, target?: number, options?: VideoLoudnessGainOptions): {
48
+ gain: number;
49
+ gainDb: number;
50
+ limitedBy?: 'max-gain' | 'peak';
51
+ };
52
+ /** Timeline stretches where words are spoken, merged across gaps shorter than `mergeGap` ms (default 500). */
53
+ export declare function videoSpeechRanges(words: readonly Pick<VideoTimelineWord, 'start' | 'end'>[], mergeGap?: number): {
54
+ from: number;
55
+ to: number;
56
+ }[];
57
+ export interface VideoDuckingOptions {
58
+ /** How far to dip under speech, in dB (default -14; -12 to -18 is usual). */
59
+ duckDb?: number;
60
+ /** How long the dip takes to come in before speech starts (ms, default 200). */
61
+ attack?: number;
62
+ /** How long the music takes to come back after speech ends (ms, default 400). */
63
+ release?: number;
64
+ /** Speech gaps shorter than this stay ducked (ms, default 600, never less than attack + release). */
65
+ mergeGap?: number;
66
+ /** The level the music sits at between dips (linear gain, default the clip's own volume or 1). */
67
+ base?: number;
68
+ }
69
+ /**
70
+ * Volume keyframes (clip time) that dip a music clip under speech: for each stretch of speech that
71
+ * overlaps it, the level eases down over `attack` ms before the speech, holds at `duckDb` and eases
72
+ * back over `release` ms after it. Stretches closer than the merge gap stay ducked between them.
73
+ * `[]` when no speech overlaps the clip. Keys are on whole ms and in time order.
74
+ */
75
+ export declare function videoDuckingKeys(clip: Pick<VideoClip, 'start' | 'duration'>, speech: readonly {
76
+ from: number;
77
+ to: number;
78
+ }[], options?: VideoDuckingOptions): VideoKey[];
79
+ export interface VideoBeatOptions {
80
+ /** Resolution of the onset scan (ms, default 10). */
81
+ resolution?: number;
82
+ /** Closest two onsets may be (ms, default 250). */
83
+ minInterval?: number;
84
+ /** How far above the local average an onset must rise, in standard deviations (default 1.5). */
85
+ sensitivity?: number;
86
+ /** Slowest and fastest tempo to consider (BPM, default 60 and 200). */
87
+ minBpm?: number;
88
+ maxBpm?: number;
89
+ }
90
+ export interface VideoBeats {
91
+ /** Times of sharp rises in level (ms, in the clock asked in): hits, plucks, kicks. */
92
+ onsets: number[];
93
+ /** The tempo the onsets repeat at, or `null` when they do not repeat. */
94
+ bpm: number | null;
95
+ /** A steady beat grid at `bpm`, through the strongest onsets (ms); empty without a tempo. */
96
+ beats: number[];
97
+ }
98
+ /**
99
+ * The beats of a stretch of sound (source ms) from its peaks: an onset envelope (rises of the peak
100
+ * level), onsets where it rises well above its local average, the tempo from the envelope's
101
+ * autocorrelation and a beat grid at that tempo, phased to fit the onsets best. Works on peaks, so
102
+ * it is quick and needs no decoding beyond what waveforms already have; it finds the pulse of
103
+ * percussive music well and of ambient music poorly.
104
+ */
105
+ export declare function findVideoAudioBeats(peaks: VideoAudioPeaks, from: number, to: number, options?: VideoBeatOptions): VideoBeats;
106
+ /** The nearest time in a sorted list to `time`, or `null` when none is within `tolerance` ms. */
107
+ export declare function nearestVideoBeat(beats: readonly number[], time: number, tolerance?: number): number | null;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Agent tools for music and the mix: beats of a music track and a plan that snaps cuts to them,
3
+ * ducking music under speech, and loudness measured and normalised the way streaming services
4
+ * measure it (LUFS). Skill: `pixerate-bgm-alignment`.
5
+ */
6
+ import type { AgentToolProvider } from '../tools/registry.cjs';
7
+ import type { VideoEditorController } from './controller.cjs';
8
+ export interface VideoMusicToolProviderOptions {
9
+ /** Provider id (unique per registry). Default `video-music`. */
10
+ id?: string;
11
+ /** Tool-name prefix (`<namespace>__<name>`); defaults to the provider id, `''` for bare names. */
12
+ namespace?: string;
13
+ /** The project cannot be edited: write tools are hidden. A string is the reason. */
14
+ readOnly?: boolean | string | (() => boolean | string);
15
+ /** The agent playback surface its calls are acted out on (default `video`). */
16
+ playbackSurface?: string;
17
+ }
18
+ /** The music and mix tools for a video editor (see the module comment). */
19
+ export declare function createVideoMusicToolProvider(controller: VideoEditorController, options?: VideoMusicToolProviderOptions): AgentToolProvider;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Agent tools for music and the mix: beats of a music track and a plan that snaps cuts to them,
3
+ * ducking music under speech, and loudness measured and normalised the way streaming services
4
+ * measure it (LUFS). Skill: `pixerate-bgm-alignment`.
5
+ */
6
+ import type { AgentToolProvider } from '../tools/registry.js';
7
+ import type { VideoEditorController } from './controller.js';
8
+ export interface VideoMusicToolProviderOptions {
9
+ /** Provider id (unique per registry). Default `video-music`. */
10
+ id?: string;
11
+ /** Tool-name prefix (`<namespace>__<name>`); defaults to the provider id, `''` for bare names. */
12
+ namespace?: string;
13
+ /** The project cannot be edited: write tools are hidden. A string is the reason. */
14
+ readOnly?: boolean | string | (() => boolean | string);
15
+ /** The agent playback surface its calls are acted out on (default `video`). */
16
+ playbackSurface?: string;
17
+ }
18
+ /** The music and mix tools for a video editor (see the module comment). */
19
+ export declare function createVideoMusicToolProvider(controller: VideoEditorController, options?: VideoMusicToolProviderOptions): AgentToolProvider;
@@ -1,5 +1,5 @@
1
1
  import { type VideoAnimatable, type VideoKeyframes } from './keyframes.cjs';
2
- import { type VideoAsset, type VideoAssetProxy, type VideoClip, type VideoClipEffect, type VideoEffectRef, type VideoEffectValues, type VideoClipAnimation, type VideoGrade, type VideoGradeLut, type VideoMarker, type VideoProject, type VideoSequence, type VideoTrack, type VideoTranscriptWord, type VideoTransform, type VideoTransition } from './types.cjs';
2
+ import { type VideoClipMask, type VideoAsset, type VideoAssetProxy, type VideoClip, type VideoClipEffect, type VideoEffectRef, type VideoEffectValues, type VideoClipAnimation, type VideoGrade, type VideoGradeLut, type VideoMarker, type VideoProject, type VideoSequence, type VideoTrack, type VideoTranscriptWord, type VideoTransform, type VideoTransition } from './types.cjs';
3
3
  /**
4
4
  * Keyframes `{ keys: [{ t, v, ease }] }`: at least one key, in strictly increasing time (clip-relative
5
5
  * ms), each value within `range`.
@@ -19,6 +19,8 @@ export declare function parseVideoTimeRemap(v: unknown, speed: unknown, path: st
19
19
  export declare function parseVideoClipAnimation(v: unknown, path: string): VideoClipAnimation | undefined;
20
20
  export declare function parseVideoGrade(v: unknown, path: string): VideoGrade | undefined;
21
21
  export declare function parseVideoGradeLut(v: unknown, path: string): VideoGradeLut | undefined;
22
+ /** A clip mask `{ shape, x, y, width, height, rotation, feather, cornerRadius, inverted, points }` (see `VideoClipMask`). */
23
+ export declare function parseVideoClipMask(v: unknown, path: string): VideoClipMask | undefined;
22
24
  export declare function parseVideoTransition(v: unknown, path: string): VideoTransition | undefined;
23
25
  /**
24
26
  * Effect control values: plain values (numbers, strings, switches, number lists) or, when
@@ -1,5 +1,5 @@
1
1
  import { type VideoAnimatable, type VideoKeyframes } from './keyframes.js';
2
- import { type VideoAsset, type VideoAssetProxy, type VideoClip, type VideoClipEffect, type VideoEffectRef, type VideoEffectValues, type VideoClipAnimation, type VideoGrade, type VideoGradeLut, type VideoMarker, type VideoProject, type VideoSequence, type VideoTrack, type VideoTranscriptWord, type VideoTransform, type VideoTransition } from './types.js';
2
+ import { type VideoClipMask, type VideoAsset, type VideoAssetProxy, type VideoClip, type VideoClipEffect, type VideoEffectRef, type VideoEffectValues, type VideoClipAnimation, type VideoGrade, type VideoGradeLut, type VideoMarker, type VideoProject, type VideoSequence, type VideoTrack, type VideoTranscriptWord, type VideoTransform, type VideoTransition } from './types.js';
3
3
  /**
4
4
  * Keyframes `{ keys: [{ t, v, ease }] }`: at least one key, in strictly increasing time (clip-relative
5
5
  * ms), each value within `range`.
@@ -19,6 +19,8 @@ export declare function parseVideoTimeRemap(v: unknown, speed: unknown, path: st
19
19
  export declare function parseVideoClipAnimation(v: unknown, path: string): VideoClipAnimation | undefined;
20
20
  export declare function parseVideoGrade(v: unknown, path: string): VideoGrade | undefined;
21
21
  export declare function parseVideoGradeLut(v: unknown, path: string): VideoGradeLut | undefined;
22
+ /** A clip mask `{ shape, x, y, width, height, rotation, feather, cornerRadius, inverted, points }` (see `VideoClipMask`). */
23
+ export declare function parseVideoClipMask(v: unknown, path: string): VideoClipMask | undefined;
22
24
  export declare function parseVideoTransition(v: unknown, path: string): VideoTransition | undefined;
23
25
  /**
24
26
  * Effect control values: plain values (numbers, strings, switches, number lists) or, when
@@ -0,0 +1,75 @@
1
+ import { type VideoIssue } from './check.cjs';
2
+ import { type VideoEdit } from './edit.cjs';
3
+ import type { VideoProject } from './types.cjs';
4
+ /**
5
+ * Plan first, apply later: a `VideoEditPlan` is a batch of edits worked out against the project
6
+ * and summarised (length before and after, clips added, removed and changed, stretches cut) without
7
+ * changing anything. People and agents review the plan, preview it, then apply it as one undo step
8
+ * or discard it. Plans are not part of the project or its history.
9
+ */
10
+ /** A stretch a plan cuts or changes (timeline ms), with what is said there or why. */
11
+ export interface VideoEditPlanRange {
12
+ from: number;
13
+ to: number;
14
+ /** The words in it, as the transcript tools return them. */
15
+ text?: string;
16
+ /** Why it is in the plan (e.g. `filler`, `pause`, `repetition`, `beat`). */
17
+ reason?: string;
18
+ }
19
+ /** What applying a plan would do. */
20
+ export interface VideoEditPlanSummary {
21
+ /** The project's length now and after (ms). */
22
+ durationBefore: number;
23
+ durationAfter: number;
24
+ /** `durationAfter - durationBefore` (negative when it shortens). */
25
+ durationDelta: number;
26
+ /** Clip ids that would be new, gone or different. */
27
+ clipsAdded: string[];
28
+ clipsRemoved: string[];
29
+ clipsChanged: string[];
30
+ /** Problems the plan would add (issues the project does not have now). */
31
+ newIssues: VideoIssue[];
32
+ /** How many edits it applies. */
33
+ edits: number;
34
+ }
35
+ /** A proposed batch of edits and what it would do. */
36
+ export interface VideoEditPlan {
37
+ /** Unique among the open plans (e.g. `plan1`). */
38
+ id: string;
39
+ /** A short label, also the undo step's label when applied. */
40
+ title: string;
41
+ /** What it does and why, for the reviewer. */
42
+ description?: string;
43
+ /** Who proposed it (`agent`, `user`, a tool name…). */
44
+ source?: string;
45
+ edits: VideoEdit[];
46
+ /** The stretches it cuts or changes, for a diff view. */
47
+ ranges?: VideoEditPlanRange[];
48
+ summary: VideoEditPlanSummary;
49
+ }
50
+ /** What changed between two versions of a project: clips added, removed and changed, and the length. */
51
+ export declare function diffVideoProjects(before: VideoProject, after: VideoProject): Omit<VideoEditPlanSummary, 'newIssues' | 'edits'>;
52
+ export interface VideoEditPlanInput {
53
+ /** Default: a fresh `plan<n>` id. */
54
+ id?: string;
55
+ title: string;
56
+ description?: string;
57
+ source?: string;
58
+ edits: readonly VideoEdit[];
59
+ ranges?: readonly VideoEditPlanRange[];
60
+ }
61
+ /**
62
+ * Works a plan out against a project without changing it: the edits are applied to a copy, all or
63
+ * nothing, and summarised. `ok: false` with the edit's error when they do not apply. `project` is
64
+ * what the project would become (to preview).
65
+ */
66
+ export declare function planVideoEdits(project: VideoProject, input: VideoEditPlanInput): {
67
+ ok: true;
68
+ plan: VideoEditPlan;
69
+ project: VideoProject;
70
+ } | {
71
+ ok: false;
72
+ error: string;
73
+ };
74
+ /** One line describing a plan's effect, e.g. "Removes 4 stretches (3.2 s shorter): 5 clips changed, 1 removed". */
75
+ export declare function describeVideoEditPlan(plan: VideoEditPlan): string;
@@ -0,0 +1,75 @@
1
+ import { type VideoIssue } from './check.js';
2
+ import { type VideoEdit } from './edit.js';
3
+ import type { VideoProject } from './types.js';
4
+ /**
5
+ * Plan first, apply later: a `VideoEditPlan` is a batch of edits worked out against the project
6
+ * and summarised (length before and after, clips added, removed and changed, stretches cut) without
7
+ * changing anything. People and agents review the plan, preview it, then apply it as one undo step
8
+ * or discard it. Plans are not part of the project or its history.
9
+ */
10
+ /** A stretch a plan cuts or changes (timeline ms), with what is said there or why. */
11
+ export interface VideoEditPlanRange {
12
+ from: number;
13
+ to: number;
14
+ /** The words in it, as the transcript tools return them. */
15
+ text?: string;
16
+ /** Why it is in the plan (e.g. `filler`, `pause`, `repetition`, `beat`). */
17
+ reason?: string;
18
+ }
19
+ /** What applying a plan would do. */
20
+ export interface VideoEditPlanSummary {
21
+ /** The project's length now and after (ms). */
22
+ durationBefore: number;
23
+ durationAfter: number;
24
+ /** `durationAfter - durationBefore` (negative when it shortens). */
25
+ durationDelta: number;
26
+ /** Clip ids that would be new, gone or different. */
27
+ clipsAdded: string[];
28
+ clipsRemoved: string[];
29
+ clipsChanged: string[];
30
+ /** Problems the plan would add (issues the project does not have now). */
31
+ newIssues: VideoIssue[];
32
+ /** How many edits it applies. */
33
+ edits: number;
34
+ }
35
+ /** A proposed batch of edits and what it would do. */
36
+ export interface VideoEditPlan {
37
+ /** Unique among the open plans (e.g. `plan1`). */
38
+ id: string;
39
+ /** A short label, also the undo step's label when applied. */
40
+ title: string;
41
+ /** What it does and why, for the reviewer. */
42
+ description?: string;
43
+ /** Who proposed it (`agent`, `user`, a tool name…). */
44
+ source?: string;
45
+ edits: VideoEdit[];
46
+ /** The stretches it cuts or changes, for a diff view. */
47
+ ranges?: VideoEditPlanRange[];
48
+ summary: VideoEditPlanSummary;
49
+ }
50
+ /** What changed between two versions of a project: clips added, removed and changed, and the length. */
51
+ export declare function diffVideoProjects(before: VideoProject, after: VideoProject): Omit<VideoEditPlanSummary, 'newIssues' | 'edits'>;
52
+ export interface VideoEditPlanInput {
53
+ /** Default: a fresh `plan<n>` id. */
54
+ id?: string;
55
+ title: string;
56
+ description?: string;
57
+ source?: string;
58
+ edits: readonly VideoEdit[];
59
+ ranges?: readonly VideoEditPlanRange[];
60
+ }
61
+ /**
62
+ * Works a plan out against a project without changing it: the edits are applied to a copy, all or
63
+ * nothing, and summarised. `ok: false` with the edit's error when they do not apply. `project` is
64
+ * what the project would become (to preview).
65
+ */
66
+ export declare function planVideoEdits(project: VideoProject, input: VideoEditPlanInput): {
67
+ ok: true;
68
+ plan: VideoEditPlan;
69
+ project: VideoProject;
70
+ } | {
71
+ ok: false;
72
+ error: string;
73
+ };
74
+ /** One line describing a plan's effect, e.g. "Removes 4 stretches (3.2 s shorter): 5 clips changed, 1 removed". */
75
+ export declare function describeVideoEditPlan(plan: VideoEditPlan): string;
@@ -54,7 +54,7 @@ export type VideoCanvasContext = Pick<CanvasRenderingContext2D, 'save' | 'restor
54
54
  */
55
55
  export interface VideoScratchCanvas {
56
56
  canvas: CanvasImageSource;
57
- ctx: VideoCanvasContext & Pick<CanvasRenderingContext2D, 'getImageData' | 'putImageData' | 'clearRect'>;
57
+ ctx: VideoCanvasContext & Pick<CanvasRenderingContext2D, 'getImageData' | 'putImageData' | 'clearRect'> & Partial<Pick<CanvasRenderingContext2D, 'moveTo' | 'lineTo' | 'closePath' | 'fill'>>;
58
58
  }
59
59
  export interface VideoDrawOptions {
60
60
  /** Puts an SVG filter (white balance) in the page, so the canvas's `filter: url(#id)` finds it. */
@@ -54,7 +54,7 @@ export type VideoCanvasContext = Pick<CanvasRenderingContext2D, 'save' | 'restor
54
54
  */
55
55
  export interface VideoScratchCanvas {
56
56
  canvas: CanvasImageSource;
57
- ctx: VideoCanvasContext & Pick<CanvasRenderingContext2D, 'getImageData' | 'putImageData' | 'clearRect'>;
57
+ ctx: VideoCanvasContext & Pick<CanvasRenderingContext2D, 'getImageData' | 'putImageData' | 'clearRect'> & Partial<Pick<CanvasRenderingContext2D, 'moveTo' | 'lineTo' | 'closePath' | 'fill'>>;
58
58
  }
59
59
  export interface VideoDrawOptions {
60
60
  /** Puts an SVG filter (white balance) in the page, so the canvas's `filter: url(#id)` finds it. */
@@ -2,6 +2,8 @@ import type { VideoAsset, VideoClip, VideoProject, VideoSequence, VideoTrack } f
2
2
  /** Clip edges closer than this (ms) count as touching: a cut, not a gap. */
3
3
  export declare const VIDEO_TIME_EPSILON = 0.5;
4
4
  export declare const videoClipEnd: (clip: VideoClip) => number;
5
+ /** What a clip is called in messages: "title", "colour", "effect clip", "adjustment clip", "compound clip" or "media clip". */
6
+ export declare const videoClipTypeLabel: (clip: Pick<VideoClip, "type">) => string;
5
7
  /** Whether a track moves with ripple edits on other tracks (`syncLocked`, default true). */
6
8
  export declare const videoTrackSyncLocked: (track: VideoTrack) => boolean;
7
9
  /** Playback speed of a clip (1 for titles and colours). */
@@ -2,6 +2,8 @@ import type { VideoAsset, VideoClip, VideoProject, VideoSequence, VideoTrack } f
2
2
  /** Clip edges closer than this (ms) count as touching: a cut, not a gap. */
3
3
  export declare const VIDEO_TIME_EPSILON = 0.5;
4
4
  export declare const videoClipEnd: (clip: VideoClip) => number;
5
+ /** What a clip is called in messages: "title", "colour", "effect clip", "adjustment clip", "compound clip" or "media clip". */
6
+ export declare const videoClipTypeLabel: (clip: Pick<VideoClip, "type">) => string;
5
7
  /** Whether a track moves with ripple edits on other tracks (`syncLocked`, default true). */
6
8
  export declare const videoTrackSyncLocked: (track: VideoTrack) => boolean;
7
9
  /** Playback speed of a clip (1 for titles and colours). */