@aicut/core 0.4.3 → 0.5.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @aicut/core
2
2
 
3
- > Framework-agnostic engine for the AiCut video editor — canvas timeline, plain-JSON projects, zero runtime deps.
3
+ > Framework-agnostic engine for the AiCut video editor — canvas timeline, plain-JSON projects, pluggable playback. Main entry has zero runtime deps; opt-in sub-entries bundle their own (three.js for `/lighting`, mp4box.js for `/webcodecs`).
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@aicut/core.svg)](https://www.npmjs.com/package/@aicut/core)
6
6
  [![License](https://img.shields.io/npm/l/@aicut/core.svg)](./LICENSE)
@@ -155,6 +155,117 @@ editor.toolbarRight.appendChild(customIconBtn);
155
155
 
156
156
  The standalone `Timeline` also exposes `toolbarLeft` / `toolbarRight` when constructed with `toolbar: true`.
157
157
 
158
+ ## Playback engine
159
+
160
+ The Editor talks to playback through a single interface (`PlaybackEngine`).
161
+ The default engine is `HtmlVideoEngine` — one hidden `<video>` per source,
162
+ swapped at clip boundaries. Zero deps, works in every browser, but seek
163
+ snaps to the nearest keyframe (the browser owns the decode pipeline).
164
+
165
+ For frame-accurate scrubbing, multi-track compositing, transitions, or a
166
+ custom render pipeline (WebGL compositor, IPC bridge to a native player,
167
+ WebRTC stream consumer), pass your own factory:
168
+
169
+ ```ts
170
+ import {
171
+ Editor,
172
+ type PlaybackEngine,
173
+ type PlaybackEngineFactory,
174
+ } from "@aicut/core";
175
+
176
+ const myFactory: PlaybackEngineFactory = ({ host, project }) => {
177
+ // host: a div the editor owns. Mount whatever surface you need.
178
+ // project: the initial Project — pre-warm decoders, etc.
179
+ return new MyEngine(host, project); // implements PlaybackEngine
180
+ };
181
+
182
+ Editor.create({ container, project, playbackEngine: myFactory });
183
+ ```
184
+
185
+ The contract — every engine implements this exactly:
186
+
187
+ ```ts
188
+ interface PlaybackEngine {
189
+ setProject(next: Project): void;
190
+ play(): void;
191
+ pause(): void;
192
+ isPlaying(): boolean;
193
+ getTime(): Ms;
194
+ seek(timeMs: Ms): void;
195
+ destroy(): void;
196
+
197
+ // Optional event hooks — Editor assigns these after construction.
198
+ onTimeUpdate?: (ms: Ms) => void;
199
+ onEnded?: () => void;
200
+ onError?: (err: Error) => void;
201
+ onReady?: () => void;
202
+ onSourceMetadata?: (sourceId: string, durationMs: Ms) => void;
203
+ }
204
+ ```
205
+
206
+ Engines that can't emit a particular event (e.g. no audio metadata)
207
+ simply never call that hook. The Editor re-emits engine events as its
208
+ own `time` / `pause` / `error` / `ready` / `change` events, so your host
209
+ code is unaffected by which engine is in use.
210
+
211
+ ### Bundled engines
212
+
213
+ | Engine | Where | Decoder | Renderer | Cost |
214
+ | --- | --- | --- | --- | --- |
215
+ | `HtmlVideoEngine` | main | browser | raw `<video>` | 0 deps |
216
+ | `CanvasCompositorEngine` | main | browser | `ctx.drawImage` | 0 deps |
217
+ | `WebCodecsEngine` | `@aicut/core/webcodecs` | `VideoDecoder` (frame-accurate) | `ctx.drawImage(VideoFrame)` | bundles mp4box.js (~200 KB) |
218
+
219
+ The WebCodecs path is on its own sub-entry so consumers who don't ask for it pay nothing for the demuxer. Feature-detect before constructing:
220
+
221
+ ```ts
222
+ import {
223
+ WebCodecsEngine,
224
+ isWebCodecsSupported,
225
+ } from "@aicut/core/webcodecs";
226
+
227
+ const factory: PlaybackEngineFactory = isWebCodecsSupported()
228
+ ? (opts) => new WebCodecsEngine({ ...opts, debug: true })
229
+ : htmlVideoEngineFactory;
230
+
231
+ Editor.create({ container, project, playbackEngine: factory });
232
+ ```
233
+
234
+ `WebCodecsEngine` v1 covers single-track MP4/MOV playback (H.264 / HEVC / VP9 / AV1 — whatever the browser's `VideoDecoder` supports). Multi-track compositing, audio, transitions land in follow-up releases on the same surface.
235
+
236
+ ## Timeline density
237
+
238
+ Defaults are tuned for desktop. For compact viewports (laptop side panels, embedded editors), shrink the bottom area and / or row height:
239
+
240
+ ```ts
241
+ Editor.create({
242
+ container,
243
+ project,
244
+ timelineHeight: 160, // outer height of the bottom timeline area
245
+ // (default 240). Scrolls internally when
246
+ // tracks overflow.
247
+ trackHeight: 40, // each track row (default 56). Affects clip
248
+ // body + thumbnail strip.
249
+ rulerHeight: 22, // time-label strip (default 24).
250
+ });
251
+ ```
252
+
253
+ | Option | Default | Useful range | Notes |
254
+ | --- | --- | --- | --- |
255
+ | `timelineHeight` | 240 | 120 – 480 | Outer height of `.aicut-timeline`. Reactive in the React + Vue wrappers — swap any time. Internal scroll appears when tracks don't fit. |
256
+ | `trackHeight` | 56 | 28 – 96 | Per-row pixel height. Applied process-wide via `setTimelineMetrics` (see below). Re-apply by remounting the editor. |
257
+ | `rulerHeight` | 24 | 18 – 36 | Time-label strip height. Same lifecycle as `trackHeight`. |
258
+
259
+ For runtime control without an editor option, call the underlying setter directly:
260
+
261
+ ```ts
262
+ import { setTimelineMetrics } from "@aicut/core";
263
+
264
+ setTimelineMetrics({ trackHeight: 36, rulerHeight: 20 });
265
+ ```
266
+
267
+ `TRACK_HEIGHT` and `RULER_HEIGHT` are ESM live bindings — re-reading them after the setter returns the updated values.
268
+
158
269
  ## Lighting picker (opt-in sub-entry)
159
270
 
160
271
  A separate component for AI-relighting workflows — drag a light dot around a 3D sphere wrapping a subject frame, control brightness / color / direction. Three.js is bundled only on this sub-entry, so consumers of the video editor pay zero bytes for it.
@@ -0,0 +1,51 @@
1
+ /**
2
+ * UI strings the editor paints into the DOM (toolbar tooltips, the
3
+ * fullscreen exit button) and onto the timeline canvas (phantom new-
4
+ * track label, track header labels). Every user-visible literal in
5
+ * `@aicut/core` flows through this interface — there are no hidden
6
+ * hard-coded translations elsewhere in the library.
7
+ *
8
+ * Defaults to English. Hosts that want Chinese (or any other locale)
9
+ * pass `locale: localeZh` to `Editor.create` / `Timeline.create`, or
10
+ * override individual keys with `locale: { undo: "撤销" }`.
11
+ */
12
+ interface Locale {
13
+ undo: string;
14
+ redo: string;
15
+ split: string;
16
+ trimLeft: string;
17
+ trimRight: string;
18
+ speedComingSoon: string;
19
+ playPause: string;
20
+ fullscreen: string;
21
+ snap: string;
22
+ /** Title shown on the snap button when snap is ON (clicking turns OFF). */
23
+ snapOnTitle: string;
24
+ /** Title shown when snap is OFF (clicking turns ON). */
25
+ snapOffTitle: string;
26
+ zoomOut: string;
27
+ zoomIn: string;
28
+ reset: string;
29
+ exitFullscreen: string;
30
+ exitFullscreenTitle: string;
31
+ /** Phantom row that appears under the last track during a drag. */
32
+ newTrack: string;
33
+ /** Track header — `{n}` is replaced with the 1-based track index. */
34
+ videoTrackLabel: string;
35
+ /** Same template format as videoTrackLabel. */
36
+ audioTrackLabel: string;
37
+ }
38
+ /** English. The library default — chosen over Chinese as the OSS norm. */
39
+ declare const localeEn: Locale;
40
+ /** Simplified Chinese. */
41
+ declare const localeZh: Locale;
42
+ /** Spread defaults under host overrides — host can supply a partial. */
43
+ declare function mergeLocale(partial: Partial<Locale> | undefined): Locale;
44
+ /**
45
+ * Replace `{key}` placeholders in a template. We only need `{n}`
46
+ * substitution today; the implementation is generic so additional
47
+ * keys (e.g. `{name}`) won't need a second pass.
48
+ */
49
+ declare function formatLabel(template: string, vars: Record<string, string | number>): string;
50
+
51
+ export { type Locale as L, localeZh as a, formatLabel as f, localeEn as l, mergeLocale as m };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * UI strings the editor paints into the DOM (toolbar tooltips, the
3
+ * fullscreen exit button) and onto the timeline canvas (phantom new-
4
+ * track label, track header labels). Every user-visible literal in
5
+ * `@aicut/core` flows through this interface — there are no hidden
6
+ * hard-coded translations elsewhere in the library.
7
+ *
8
+ * Defaults to English. Hosts that want Chinese (or any other locale)
9
+ * pass `locale: localeZh` to `Editor.create` / `Timeline.create`, or
10
+ * override individual keys with `locale: { undo: "撤销" }`.
11
+ */
12
+ interface Locale {
13
+ undo: string;
14
+ redo: string;
15
+ split: string;
16
+ trimLeft: string;
17
+ trimRight: string;
18
+ speedComingSoon: string;
19
+ playPause: string;
20
+ fullscreen: string;
21
+ snap: string;
22
+ /** Title shown on the snap button when snap is ON (clicking turns OFF). */
23
+ snapOnTitle: string;
24
+ /** Title shown when snap is OFF (clicking turns ON). */
25
+ snapOffTitle: string;
26
+ zoomOut: string;
27
+ zoomIn: string;
28
+ reset: string;
29
+ exitFullscreen: string;
30
+ exitFullscreenTitle: string;
31
+ /** Phantom row that appears under the last track during a drag. */
32
+ newTrack: string;
33
+ /** Track header — `{n}` is replaced with the 1-based track index. */
34
+ videoTrackLabel: string;
35
+ /** Same template format as videoTrackLabel. */
36
+ audioTrackLabel: string;
37
+ }
38
+ /** English. The library default — chosen over Chinese as the OSS norm. */
39
+ declare const localeEn: Locale;
40
+ /** Simplified Chinese. */
41
+ declare const localeZh: Locale;
42
+ /** Spread defaults under host overrides — host can supply a partial. */
43
+ declare function mergeLocale(partial: Partial<Locale> | undefined): Locale;
44
+ /**
45
+ * Replace `{key}` placeholders in a template. We only need `{n}`
46
+ * substitution today; the implementation is generic so additional
47
+ * keys (e.g. `{name}`) won't need a second pass.
48
+ */
49
+ declare function formatLabel(template: string, vars: Record<string, string | number>): string;
50
+
51
+ export { type Locale as L, localeZh as a, formatLabel as f, localeEn as l, mergeLocale as m };