@aicut/core 0.4.3 → 0.6.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.
@@ -0,0 +1,108 @@
1
+ import { P as Project, M as Ms } from './types-CmS-UIEr.js';
2
+
3
+ /**
4
+ * Construction context the Editor hands to a playback engine. The
5
+ * engine mounts itself into `host` (typically the editor's preview
6
+ * element) and owns whatever DOM / canvas / video elements it needs.
7
+ */
8
+ interface PlaybackEngineOptions {
9
+ /** Mount point. Engine appends its preview surface here. */
10
+ host: HTMLElement;
11
+ /** Initial project — engine pre-warms sources, etc. */
12
+ project: Project;
13
+ }
14
+ /**
15
+ * The contract every preview engine satisfies. Editor talks ONLY
16
+ * through this surface — implementations are interchangeable.
17
+ *
18
+ * Built-in implementations:
19
+ * - `HtmlVideoEngine` default; one HTMLVideoElement per source,
20
+ * swap on clip boundaries. Zero deps,
21
+ * GPU-accelerated decode by the browser, but
22
+ * seek snaps to keyframes (browser controls
23
+ * the decode pipeline).
24
+ * - `WebCodecsEngine` opt-in (v0.6+); manual VideoDecoder loop +
25
+ * canvas blit. Frame-accurate seek; will
26
+ * underpin multi-track compositing, transitions,
27
+ * and shaders in later versions.
28
+ *
29
+ * Hosts can ship their own implementation (e.g., a WebGL compositor,
30
+ * a WebRTC stream consumer, a desktop-wrapper IPC bridge) and inject
31
+ * it via `Editor.create({ playbackEngine: myFactory })`.
32
+ */
33
+ interface PlaybackEngine {
34
+ /** Replace the project. Engine re-warms sources + re-resolves the
35
+ * active clip for the current playhead. Idempotent. */
36
+ setProject(next: Project): void;
37
+ play(): void;
38
+ pause(): void;
39
+ isPlaying(): boolean;
40
+ /** Current playhead (ms from project start). */
41
+ getTime(): Ms;
42
+ /** Move the playhead. Engine clamps to [0, totalDuration]. */
43
+ seek(timeMs: Ms): void;
44
+ /** Free all resources (DOM nodes, decoders, AudioContexts, rAF). */
45
+ destroy(): void;
46
+ /**
47
+ * Optional. The **output frame rect** — the fixed bounds anything
48
+ * the engine renders is clipped to. The user's keyframe X / Y /
49
+ * scale move the content WITHIN this frame (think picture-in-
50
+ * picture, pan, zoom); anything outside is hidden by the engine.
51
+ *
52
+ * This rect does NOT change with the active transform — it's
53
+ * the stage. The overlay's dashed border is drawn here. Coords
54
+ * relative to `opts.host`. Returns null when no clip is active.
55
+ */
56
+ getOutputFrameRect?(): {
57
+ x: number;
58
+ y: number;
59
+ w: number;
60
+ h: number;
61
+ } | null;
62
+ /**
63
+ * Optional. Return the screen-space CSS-pixel rectangle of the
64
+ * actually-rendered content (the video frame after the active
65
+ * keyframe transform is applied). May extend outside the output
66
+ * frame — the engine clips to the output frame at paint time, but
67
+ * the overlay still wants the geometric rect to position scale
68
+ * handles on the visible content corners.
69
+ *
70
+ * Returns null when no clip is active. Engines that don't expose
71
+ * this leave keyframe handles attached to the output frame instead.
72
+ */
73
+ getFrameRect?(): {
74
+ x: number;
75
+ y: number;
76
+ w: number;
77
+ h: number;
78
+ } | null;
79
+ /** Fired on each rAF / decoded frame with the current playhead. */
80
+ onTimeUpdate?: (ms: Ms) => void;
81
+ /** Fired once when the project's end is reached during playback. */
82
+ onEnded?: () => void;
83
+ /** Decode / network / capability failures. */
84
+ onError?: (err: Error) => void;
85
+ /**
86
+ * Fired the first time a fresh playback target is "ready to play"
87
+ * — analogue of HTMLMediaElement's `loadedmetadata`. Editor uses
88
+ * it to gate scaling / auto-fit work.
89
+ */
90
+ onReady?: () => void;
91
+ /**
92
+ * Fired when an individual source's duration becomes known. Editor
93
+ * folds this into the project model so the timeline can size clips
94
+ * correctly even when the host didn't ship a `duration` upfront.
95
+ */
96
+ onSourceMetadata?: (sourceId: string, durationMs: Ms) => void;
97
+ }
98
+ /**
99
+ * A factory that builds an engine for a given mount/project. Hosts
100
+ * pass one of these to `Editor.create({ playbackEngine })` to swap
101
+ * implementations. The factory shape — rather than a class reference
102
+ * — keeps Editor decoupled from constructor signatures and lets
103
+ * factories close over host-side configuration (auth tokens, render
104
+ * backend URL, custom shaders, etc.) without polluting the interface.
105
+ */
106
+ type PlaybackEngineFactory = (opts: PlaybackEngineOptions) => PlaybackEngine;
107
+
108
+ export type { PlaybackEngine as P, PlaybackEngineOptions as a, PlaybackEngineFactory as b };
@@ -0,0 +1,108 @@
1
+ import { P as Project, M as Ms } from './types-CmS-UIEr.cjs';
2
+
3
+ /**
4
+ * Construction context the Editor hands to a playback engine. The
5
+ * engine mounts itself into `host` (typically the editor's preview
6
+ * element) and owns whatever DOM / canvas / video elements it needs.
7
+ */
8
+ interface PlaybackEngineOptions {
9
+ /** Mount point. Engine appends its preview surface here. */
10
+ host: HTMLElement;
11
+ /** Initial project — engine pre-warms sources, etc. */
12
+ project: Project;
13
+ }
14
+ /**
15
+ * The contract every preview engine satisfies. Editor talks ONLY
16
+ * through this surface — implementations are interchangeable.
17
+ *
18
+ * Built-in implementations:
19
+ * - `HtmlVideoEngine` default; one HTMLVideoElement per source,
20
+ * swap on clip boundaries. Zero deps,
21
+ * GPU-accelerated decode by the browser, but
22
+ * seek snaps to keyframes (browser controls
23
+ * the decode pipeline).
24
+ * - `WebCodecsEngine` opt-in (v0.6+); manual VideoDecoder loop +
25
+ * canvas blit. Frame-accurate seek; will
26
+ * underpin multi-track compositing, transitions,
27
+ * and shaders in later versions.
28
+ *
29
+ * Hosts can ship their own implementation (e.g., a WebGL compositor,
30
+ * a WebRTC stream consumer, a desktop-wrapper IPC bridge) and inject
31
+ * it via `Editor.create({ playbackEngine: myFactory })`.
32
+ */
33
+ interface PlaybackEngine {
34
+ /** Replace the project. Engine re-warms sources + re-resolves the
35
+ * active clip for the current playhead. Idempotent. */
36
+ setProject(next: Project): void;
37
+ play(): void;
38
+ pause(): void;
39
+ isPlaying(): boolean;
40
+ /** Current playhead (ms from project start). */
41
+ getTime(): Ms;
42
+ /** Move the playhead. Engine clamps to [0, totalDuration]. */
43
+ seek(timeMs: Ms): void;
44
+ /** Free all resources (DOM nodes, decoders, AudioContexts, rAF). */
45
+ destroy(): void;
46
+ /**
47
+ * Optional. The **output frame rect** — the fixed bounds anything
48
+ * the engine renders is clipped to. The user's keyframe X / Y /
49
+ * scale move the content WITHIN this frame (think picture-in-
50
+ * picture, pan, zoom); anything outside is hidden by the engine.
51
+ *
52
+ * This rect does NOT change with the active transform — it's
53
+ * the stage. The overlay's dashed border is drawn here. Coords
54
+ * relative to `opts.host`. Returns null when no clip is active.
55
+ */
56
+ getOutputFrameRect?(): {
57
+ x: number;
58
+ y: number;
59
+ w: number;
60
+ h: number;
61
+ } | null;
62
+ /**
63
+ * Optional. Return the screen-space CSS-pixel rectangle of the
64
+ * actually-rendered content (the video frame after the active
65
+ * keyframe transform is applied). May extend outside the output
66
+ * frame — the engine clips to the output frame at paint time, but
67
+ * the overlay still wants the geometric rect to position scale
68
+ * handles on the visible content corners.
69
+ *
70
+ * Returns null when no clip is active. Engines that don't expose
71
+ * this leave keyframe handles attached to the output frame instead.
72
+ */
73
+ getFrameRect?(): {
74
+ x: number;
75
+ y: number;
76
+ w: number;
77
+ h: number;
78
+ } | null;
79
+ /** Fired on each rAF / decoded frame with the current playhead. */
80
+ onTimeUpdate?: (ms: Ms) => void;
81
+ /** Fired once when the project's end is reached during playback. */
82
+ onEnded?: () => void;
83
+ /** Decode / network / capability failures. */
84
+ onError?: (err: Error) => void;
85
+ /**
86
+ * Fired the first time a fresh playback target is "ready to play"
87
+ * — analogue of HTMLMediaElement's `loadedmetadata`. Editor uses
88
+ * it to gate scaling / auto-fit work.
89
+ */
90
+ onReady?: () => void;
91
+ /**
92
+ * Fired when an individual source's duration becomes known. Editor
93
+ * folds this into the project model so the timeline can size clips
94
+ * correctly even when the host didn't ship a `duration` upfront.
95
+ */
96
+ onSourceMetadata?: (sourceId: string, durationMs: Ms) => void;
97
+ }
98
+ /**
99
+ * A factory that builds an engine for a given mount/project. Hosts
100
+ * pass one of these to `Editor.create({ playbackEngine })` to swap
101
+ * implementations. The factory shape — rather than a class reference
102
+ * — keeps Editor decoupled from constructor signatures and lets
103
+ * factories close over host-side configuration (auth tokens, render
104
+ * backend URL, custom shaders, etc.) without polluting the interface.
105
+ */
106
+ type PlaybackEngineFactory = (opts: PlaybackEngineOptions) => PlaybackEngine;
107
+
108
+ export type { PlaybackEngine as P, PlaybackEngineOptions as a, PlaybackEngineFactory as b };
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Milliseconds. All timing in the project is expressed as integer ms to
3
+ * keep JSON serialization unambiguous (no frame-rate coupling in the
4
+ * data model — the renderer can present time as frames if it wants).
5
+ */
6
+ type Ms = number;
7
+ interface MediaSource {
8
+ id: string;
9
+ url: string;
10
+ kind: "video" | "audio";
11
+ /** Optional — probed lazily from the <video> element if absent. */
12
+ duration?: Ms;
13
+ name?: string;
14
+ }
15
+ interface Clip {
16
+ id: string;
17
+ sourceId: string;
18
+ /** Window into the source — `in` inclusive, `out` exclusive. */
19
+ in: Ms;
20
+ out: Ms;
21
+ /** Position on the timeline. */
22
+ start: Ms;
23
+ /**
24
+ * Playback rate. 1 = normal, 2 = 2× speed. Default 1.
25
+ * Persisted in the project JSON so a host can restore exactly.
26
+ */
27
+ speed?: number;
28
+ /**
29
+ * Static base values for the content transform, used when the clip
30
+ * has no keyframes for that property. Pan slides the video inside
31
+ * the FIXED output frame (the dashed border the user sees); scale
32
+ * grows / shrinks the content around the frame center. Anything
33
+ * pushed outside the output frame is clipped — that's how
34
+ * picture-in-picture, pan, and zoom work.
35
+ *
36
+ * `panX`, `panY` are CSS pixels relative to the output frame center.
37
+ * Defaults: panX = 0, panY = 0, scale = 1 (content fills frame).
38
+ */
39
+ panX?: number;
40
+ panY?: number;
41
+ scale?: number;
42
+ /**
43
+ * Per-property keyframe animation. Each keyframe targets one prop
44
+ * (`panX`, `panY`, or `scale`) so the user can animate one axis
45
+ * independently of the others (the standard NLE / CapCut model).
46
+ *
47
+ * Times are clip-local (0 = clip's `in`), so trim and move ops
48
+ * carry keyframes with the clip. Empty array / undefined = use the
49
+ * static base values above. `normalizeProject` keeps it sorted by
50
+ * (prop, time).
51
+ */
52
+ keyframes?: Keyframe[];
53
+ }
54
+ /** Properties on a Clip that can be animated by keyframes. */
55
+ type KeyframeProp = "panX" | "panY" | "scale";
56
+ /**
57
+ * Easing curve that shapes the segment LEAVING this keyframe — i.e.
58
+ * the curve from this keyframe to the next one in time. Matches the
59
+ * "outgoing" easing model in After Effects / Premiere / CapCut so a
60
+ * single dropdown per keyframe is enough.
61
+ *
62
+ * - `linear` — constant rate (default)
63
+ * - `easeIn` — start slow, finish fast (cubic)
64
+ * - `easeOut` — start fast, finish slow (cubic)
65
+ * - `easeInOut` — slow on both ends, fast in the middle (cubic)
66
+ */
67
+ type EasingKind = "linear" | "easeIn" | "easeOut" | "easeInOut";
68
+ /**
69
+ * One pinned value for one property at one moment in clip-local time.
70
+ * Properties without keyframes fall back to the clip's static base
71
+ * value (`Clip.panX` / `panY` / `scale`).
72
+ */
73
+ interface Keyframe {
74
+ id: string;
75
+ /** Which property this keyframe controls. */
76
+ prop: KeyframeProp;
77
+ /** Clip-local time in ms. 0 = clip's `in`. Bounds: [0, clip.out - clip.in]. */
78
+ time: Ms;
79
+ /** The value the property holds at this moment. Same units as the
80
+ * matching static field (CSS px for pan, multiplier for scale). */
81
+ value: number;
82
+ /** Easing curve for the segment leaving THIS keyframe toward the
83
+ * next one. Optional — omitted / undefined = "linear" (back-compat
84
+ * with projects authored before the easing field existed). */
85
+ easing?: EasingKind;
86
+ }
87
+ interface Track {
88
+ id: string;
89
+ kind: "video" | "audio";
90
+ /** Clips on this track. Must be kept sorted by `start` and non-overlapping. */
91
+ clips: Clip[];
92
+ }
93
+ interface Project {
94
+ /** Schema version — bump when breaking the JSON shape. */
95
+ version: 1;
96
+ sources: MediaSource[];
97
+ tracks: Track[];
98
+ /**
99
+ * Project frame rate. Drives keyboard frame-stepping (← / →), the
100
+ * future timecode display, and ffmpeg compilation of keyframe
101
+ * animations. Optional for back-compat — projects without `fps`
102
+ * default to 30, matching consumer NLE convention (CapCut /
103
+ * Premiere project defaults). `normalizeProject` does NOT fill
104
+ * this in, so a missing field stays missing through round-trips.
105
+ */
106
+ fps?: number;
107
+ }
108
+ /**
109
+ * Subset of CSS variables the editor honors. Pass any custom values
110
+ * via `Editor` options; everything is forwarded as `--aicut-*` on the
111
+ * editor's root container, so a host can also override via plain CSS.
112
+ */
113
+ interface Theme {
114
+ brand?: string;
115
+ secondary?: string;
116
+ surface?: string;
117
+ dark?: string;
118
+ muted?: string;
119
+ card?: string;
120
+ success?: string;
121
+ warning?: string;
122
+ info?: string;
123
+ error?: string;
124
+ /** Toolbar / ruler chrome. Background of the editor frame. */
125
+ controlsBg?: string;
126
+ controlsBorder?: string;
127
+ controlsText?: string;
128
+ controlsHover?: string;
129
+ controlsActive?: string;
130
+ /** Letterbox color around the preview video. Defaults to black. */
131
+ previewBg?: string;
132
+ radiusSm?: string;
133
+ radiusMd?: string;
134
+ radiusLg?: string;
135
+ }
136
+
137
+ export type { Clip as C, EasingKind as E, KeyframeProp as K, Ms as M, Project as P, Track as T, MediaSource as a, Theme as b, Keyframe as c };
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Milliseconds. All timing in the project is expressed as integer ms to
3
+ * keep JSON serialization unambiguous (no frame-rate coupling in the
4
+ * data model — the renderer can present time as frames if it wants).
5
+ */
6
+ type Ms = number;
7
+ interface MediaSource {
8
+ id: string;
9
+ url: string;
10
+ kind: "video" | "audio";
11
+ /** Optional — probed lazily from the <video> element if absent. */
12
+ duration?: Ms;
13
+ name?: string;
14
+ }
15
+ interface Clip {
16
+ id: string;
17
+ sourceId: string;
18
+ /** Window into the source — `in` inclusive, `out` exclusive. */
19
+ in: Ms;
20
+ out: Ms;
21
+ /** Position on the timeline. */
22
+ start: Ms;
23
+ /**
24
+ * Playback rate. 1 = normal, 2 = 2× speed. Default 1.
25
+ * Persisted in the project JSON so a host can restore exactly.
26
+ */
27
+ speed?: number;
28
+ /**
29
+ * Static base values for the content transform, used when the clip
30
+ * has no keyframes for that property. Pan slides the video inside
31
+ * the FIXED output frame (the dashed border the user sees); scale
32
+ * grows / shrinks the content around the frame center. Anything
33
+ * pushed outside the output frame is clipped — that's how
34
+ * picture-in-picture, pan, and zoom work.
35
+ *
36
+ * `panX`, `panY` are CSS pixels relative to the output frame center.
37
+ * Defaults: panX = 0, panY = 0, scale = 1 (content fills frame).
38
+ */
39
+ panX?: number;
40
+ panY?: number;
41
+ scale?: number;
42
+ /**
43
+ * Per-property keyframe animation. Each keyframe targets one prop
44
+ * (`panX`, `panY`, or `scale`) so the user can animate one axis
45
+ * independently of the others (the standard NLE / CapCut model).
46
+ *
47
+ * Times are clip-local (0 = clip's `in`), so trim and move ops
48
+ * carry keyframes with the clip. Empty array / undefined = use the
49
+ * static base values above. `normalizeProject` keeps it sorted by
50
+ * (prop, time).
51
+ */
52
+ keyframes?: Keyframe[];
53
+ }
54
+ /** Properties on a Clip that can be animated by keyframes. */
55
+ type KeyframeProp = "panX" | "panY" | "scale";
56
+ /**
57
+ * Easing curve that shapes the segment LEAVING this keyframe — i.e.
58
+ * the curve from this keyframe to the next one in time. Matches the
59
+ * "outgoing" easing model in After Effects / Premiere / CapCut so a
60
+ * single dropdown per keyframe is enough.
61
+ *
62
+ * - `linear` — constant rate (default)
63
+ * - `easeIn` — start slow, finish fast (cubic)
64
+ * - `easeOut` — start fast, finish slow (cubic)
65
+ * - `easeInOut` — slow on both ends, fast in the middle (cubic)
66
+ */
67
+ type EasingKind = "linear" | "easeIn" | "easeOut" | "easeInOut";
68
+ /**
69
+ * One pinned value for one property at one moment in clip-local time.
70
+ * Properties without keyframes fall back to the clip's static base
71
+ * value (`Clip.panX` / `panY` / `scale`).
72
+ */
73
+ interface Keyframe {
74
+ id: string;
75
+ /** Which property this keyframe controls. */
76
+ prop: KeyframeProp;
77
+ /** Clip-local time in ms. 0 = clip's `in`. Bounds: [0, clip.out - clip.in]. */
78
+ time: Ms;
79
+ /** The value the property holds at this moment. Same units as the
80
+ * matching static field (CSS px for pan, multiplier for scale). */
81
+ value: number;
82
+ /** Easing curve for the segment leaving THIS keyframe toward the
83
+ * next one. Optional — omitted / undefined = "linear" (back-compat
84
+ * with projects authored before the easing field existed). */
85
+ easing?: EasingKind;
86
+ }
87
+ interface Track {
88
+ id: string;
89
+ kind: "video" | "audio";
90
+ /** Clips on this track. Must be kept sorted by `start` and non-overlapping. */
91
+ clips: Clip[];
92
+ }
93
+ interface Project {
94
+ /** Schema version — bump when breaking the JSON shape. */
95
+ version: 1;
96
+ sources: MediaSource[];
97
+ tracks: Track[];
98
+ /**
99
+ * Project frame rate. Drives keyboard frame-stepping (← / →), the
100
+ * future timecode display, and ffmpeg compilation of keyframe
101
+ * animations. Optional for back-compat — projects without `fps`
102
+ * default to 30, matching consumer NLE convention (CapCut /
103
+ * Premiere project defaults). `normalizeProject` does NOT fill
104
+ * this in, so a missing field stays missing through round-trips.
105
+ */
106
+ fps?: number;
107
+ }
108
+ /**
109
+ * Subset of CSS variables the editor honors. Pass any custom values
110
+ * via `Editor` options; everything is forwarded as `--aicut-*` on the
111
+ * editor's root container, so a host can also override via plain CSS.
112
+ */
113
+ interface Theme {
114
+ brand?: string;
115
+ secondary?: string;
116
+ surface?: string;
117
+ dark?: string;
118
+ muted?: string;
119
+ card?: string;
120
+ success?: string;
121
+ warning?: string;
122
+ info?: string;
123
+ error?: string;
124
+ /** Toolbar / ruler chrome. Background of the editor frame. */
125
+ controlsBg?: string;
126
+ controlsBorder?: string;
127
+ controlsText?: string;
128
+ controlsHover?: string;
129
+ controlsActive?: string;
130
+ /** Letterbox color around the preview video. Defaults to black. */
131
+ previewBg?: string;
132
+ radiusSm?: string;
133
+ radiusMd?: string;
134
+ radiusLg?: string;
135
+ }
136
+
137
+ export type { Clip as C, EasingKind as E, KeyframeProp as K, Ms as M, Project as P, Track as T, MediaSource as a, Theme as b, Keyframe as c };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aicut/core",
3
- "version": "0.4.3",
4
- "description": "Framework-agnostic core for the AiCut video editor — canvas timeline, data model, HTML5 playback engine, plus an opt-in 3D lighting picker.",
3
+ "version": "0.6.0",
4
+ "description": "Framework-agnostic core for the AiCut video editor — canvas timeline, data model, pluggable PlaybackEngine (HTML5 / Canvas / WebCodecs), plus opt-in 3D lighting picker.",
5
5
  "license": "MIT",
6
6
  "author": "ziqiang <ziqiangytu@gmail.com>",
7
7
  "homepage": "https://github.com/ziqiangai/AiCut#readme",
@@ -57,6 +57,11 @@
57
57
  "import": "./dist/lighting/index.js",
58
58
  "require": "./dist/lighting/index.cjs"
59
59
  },
60
+ "./webcodecs": {
61
+ "types": "./dist/playback/webcodecs/index.d.ts",
62
+ "import": "./dist/playback/webcodecs/index.js",
63
+ "require": "./dist/playback/webcodecs/index.cjs"
64
+ },
60
65
  "./styles.css": "./styles/theme.css"
61
66
  },
62
67
  "files": [
@@ -65,12 +70,16 @@
65
70
  "README.md"
66
71
  ],
67
72
  "dependencies": {
73
+ "mp4box": "^2.4.1",
68
74
  "three": "^0.159.0"
69
75
  },
70
76
  "devDependencies": {
71
77
  "@types/three": "^0.159.0",
78
+ "@vitest/coverage-v8": "^4.1.9",
79
+ "happy-dom": "^20.10.6",
72
80
  "tsup": "^8.3.5",
73
- "typescript": "^5.7.2"
81
+ "typescript": "^5.7.2",
82
+ "vitest": "^4.1.9"
74
83
  },
75
84
  "publishConfig": {
76
85
  "access": "public"
@@ -78,6 +87,8 @@
78
87
  "scripts": {
79
88
  "build": "tsup",
80
89
  "dev": "tsup --watch",
81
- "typecheck": "tsc --noEmit"
90
+ "typecheck": "tsc --noEmit",
91
+ "test": "vitest run",
92
+ "test:watch": "vitest"
82
93
  }
83
94
  }