@cut-workbench/core 0.3.1 → 0.4.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 (67) hide show
  1. package/dist/chunks/{canvas-size-IzoEg2cq.cjs → canvas-size-DIABzEW2.cjs} +209 -302
  2. package/dist/chunks/canvas-size-DIABzEW2.cjs.map +1 -0
  3. package/dist/chunks/{canvas-size-iWL8zZPl.js → canvas-size-Dlnng74w.js} +168 -171
  4. package/dist/chunks/canvas-size-Dlnng74w.js.map +1 -0
  5. package/dist/chunks/id-CJsdlGei.js +35 -0
  6. package/dist/chunks/id-CJsdlGei.js.map +1 -0
  7. package/dist/chunks/id-DaCxD4-5.cjs +70 -0
  8. package/dist/chunks/id-DaCxD4-5.cjs.map +1 -0
  9. package/dist/chunks/rolldown-runtime-B2SfzNBj.cjs +17 -0
  10. package/dist/chunks/rolldown-runtime-D8jQpDG7.js +12 -0
  11. package/dist/chunks/{snap-CeYuTN6d.cjs → snap-Cfk15JVg.cjs} +3 -23
  12. package/dist/chunks/{snap-CeYuTN6d.cjs.map → snap-Cfk15JVg.cjs.map} +1 -1
  13. package/dist/chunks/{snap-Dc022smD.js → snap-DpkVPVJN.js} +3 -17
  14. package/dist/chunks/{snap-Dc022smD.js.map → snap-DpkVPVJN.js.map} +1 -1
  15. package/dist/chunks/srt-D539yJ-n.js +51 -0
  16. package/dist/chunks/srt-D539yJ-n.js.map +1 -0
  17. package/dist/chunks/srt-DaL36mXN.cjs +73 -0
  18. package/dist/chunks/srt-DaL36mXN.cjs.map +1 -0
  19. package/dist/chunks/thumbnails-DEjnao5r.cjs +81 -0
  20. package/dist/chunks/thumbnails-DEjnao5r.cjs.map +1 -0
  21. package/dist/chunks/thumbnails-qH0dX_Zs.js +64 -0
  22. package/dist/chunks/thumbnails-qH0dX_Zs.js.map +1 -0
  23. package/dist/chunks/time-CYEd91qN.cjs +196 -0
  24. package/dist/chunks/time-CYEd91qN.cjs.map +1 -0
  25. package/dist/chunks/time-ChF1GwCy.js +113 -0
  26. package/dist/chunks/time-ChF1GwCy.js.map +1 -0
  27. package/dist/chunks/{waveform-Bhvv2t7-.cjs → waveform-BYwDdKpC.cjs} +150 -31
  28. package/dist/chunks/waveform-BYwDdKpC.cjs.map +1 -0
  29. package/dist/chunks/{waveform-CWU8Fa-n.js → waveform-uyGNE01H.js} +111 -22
  30. package/dist/chunks/waveform-uyGNE01H.js.map +1 -0
  31. package/dist/editor/create-editor.d.ts +8 -106
  32. package/dist/editor/editor-store.d.ts +4 -0
  33. package/dist/editor/handle-types.d.ts +256 -0
  34. package/dist/engine/index.cjs +1185 -455
  35. package/dist/engine/index.cjs.map +1 -1
  36. package/dist/engine/index.d.ts +3 -0
  37. package/dist/engine/index.js +1169 -446
  38. package/dist/engine/index.js.map +1 -1
  39. package/dist/engine/webav/export-runner.d.ts +61 -0
  40. package/dist/engine/webav/exporter.d.ts +3 -1
  41. package/dist/engine/webav/import-files.d.ts +58 -0
  42. package/dist/engine/webav/import-pipeline.d.ts +23 -1
  43. package/dist/engine/webav/types.d.ts +15 -0
  44. package/dist/errors.d.ts +12 -0
  45. package/dist/export/types.d.ts +49 -0
  46. package/dist/index.cjs +83 -68
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.d.ts +6 -0
  49. package/dist/index.js +47 -43
  50. package/dist/index.js.map +1 -1
  51. package/dist/lib/error.d.ts +6 -0
  52. package/dist/media/thumbnails.d.ts +8 -0
  53. package/dist/ports/host-bridge.d.ts +7 -0
  54. package/dist/project/file-storage.d.ts +23 -0
  55. package/dist/project/schema.d.ts +16 -4
  56. package/dist/timeline/index.cjs +24 -19
  57. package/dist/timeline/index.d.ts +1 -0
  58. package/dist/timeline/index.js +4 -3
  59. package/dist/timeline/normalize.d.ts +28 -0
  60. package/dist/timeline/ops.d.ts +0 -4
  61. package/dist/timeline/snap.d.ts +0 -2
  62. package/dist/types.d.ts +14 -2
  63. package/package.json +3 -2
  64. package/dist/chunks/canvas-size-IzoEg2cq.cjs.map +0 -1
  65. package/dist/chunks/canvas-size-iWL8zZPl.js.map +0 -1
  66. package/dist/chunks/waveform-Bhvv2t7-.cjs.map +0 -1
  67. package/dist/chunks/waveform-CWU8Fa-n.js.map +0 -1
@@ -6,119 +6,21 @@
6
6
  * - 暴露命令式 API(play / seek / addFiles / export / undo …)
7
7
  * - 通过事件总线对外广播状态变化
8
8
  *
9
- * 设计原则:命令直接调 store / runtime,**不**经过 React。这样:
9
+ * 设计原则:命令直接调 store / runtime,**不经过 React**。这样:
10
10
  * - `initCutWorkbench()` 这种非 React 接入方式可以完整操作编辑器;
11
11
  * - 事件总线成为唯一对外通知渠道,接入方不必知道内部 store 结构。
12
+ *
13
+ * 文件导入与导出会**懒加载**引擎的对应模块:宿主调 `addFiles()` / `export()`
14
+ * 时不需要自己写 `await import('@cut-workbench/core/engine')`。
12
15
  */
13
- import type { StoreApi } from 'zustand/vanilla';
14
- import type { MediaAsset, Project, TimelineClip } from '../types';
15
- import type { SerializedProject } from '../project/schema';
16
- import { type EditorState } from './editor-store';
17
- import { type HistoryStore } from './history-store';
18
16
  import { type EditorRuntime } from './runtime';
19
- import { type EventEmitterView } from './event-bus';
20
- import type { StoragePort } from '../project/storage';
21
- import type { HostBridge } from '../ports/host-bridge';
22
- /** 对外事件表。新增事件时必须同步更新此接口。 */
23
- export interface CutEventMap {
24
- ready: {
25
- editorId: string;
26
- };
27
- 'project:changed': {
28
- project: Project;
29
- dirty: boolean;
30
- };
31
- 'assets:changed': {
32
- assets: MediaAsset[];
33
- };
34
- 'selection:changed': {
35
- clipIds: string[];
36
- assetId: string | null;
37
- };
38
- 'playhead:changed': {
39
- us: number;
40
- playing: boolean;
41
- };
42
- 'history:changed': {
43
- canUndo: boolean;
44
- canRedo: boolean;
45
- };
46
- 'export:start': {
47
- totalUs: number;
48
- };
49
- 'export:progress': {
50
- progress: number;
51
- };
52
- 'export:done': {
53
- blob: Blob;
54
- filename: string;
55
- };
56
- 'export:error': {
57
- message: string;
58
- };
59
- error: {
60
- scope: string;
61
- error: unknown;
62
- };
63
- dispose: Record<string, never>;
64
- }
65
- export interface EditorOptions {
66
- /** 宿主桥;不传则使用浏览器内置实现 */
67
- bridge?: HostBridge;
68
- /** 工程持久化端口;不传则用内存实现(不落盘) */
69
- storage?: StoragePort;
70
- /** 持久化命名空间(多项目 / 多实例隔离) */
71
- namespace?: string;
72
- /** 初始工程 */
73
- project?: Project;
74
- /** 初始素材 */
75
- assets?: MediaAsset[];
76
- /** 首次初始化后是否自动从 storage 加载 */
77
- autoLoad?: boolean;
78
- /** 初始化时立即载入的文件 */
79
- initialFiles?: File[];
80
- }
81
- export interface CutEditorHandle {
82
- readonly id: string;
83
- readonly store: StoreApi<EditorState>;
84
- readonly history: HistoryStore;
85
- readonly runtime: EditorRuntime;
86
- readonly events: EventEmitterView<CutEventMap>;
87
- readonly bridge: HostBridge;
88
- readonly storage: StoragePort;
89
- getProject(): Project;
90
- getAssets(): MediaAsset[];
91
- getState(): EditorState;
92
- setProject(project: Project, assets: MediaAsset[], missingAssetIds?: string[]): void;
93
- serialize(): SerializedProject;
94
- load(project: SerializedProject | Project): Promise<void>;
95
- save(): Promise<{
96
- ok: boolean;
97
- error?: string;
98
- }>;
99
- play(): void;
100
- pause(): void;
101
- toggle(): void;
102
- seek(us: number): void;
103
- getPlayhead(): number;
104
- getDuration(): number;
105
- isPlaying(): boolean;
106
- undo(): void;
107
- redo(): void;
108
- addClip(clip: TimelineClip): void;
109
- removeClips(ids: string[]): void;
110
- selectClips(ids: string[]): void;
111
- dispose(): Promise<void>;
112
- }
113
- export interface CreateEditorResult {
114
- editor: CutEditorHandle;
115
- /** 运行时的 WebAV 模块(懒加载完成后填充) */
116
- engine: Promise<unknown> | null;
117
- }
17
+ import type { AddFilesOptions, AddFilesResult, AddSubtitleAssetParams, AddTextAssetParams, AutoSaveOptions, CutEditorHandle, CutEventMap, EditorCommands, EditorOptions, ImportJsonOptions, ImportJsonResult, SaveAsResult } from './handle-types';
18
+ export type { AddFilesOptions, AddFilesResult, AddSubtitleAssetParams, AddTextAssetParams, AutoSaveOptions, CutEditorHandle, CutEventMap, EditorCommands, EditorOptions, ImportJsonOptions, ImportJsonResult, SaveAsResult, };
19
+ export type { EditorRuntime };
118
20
  /**
119
21
  * 创建编辑器实例。
120
22
  *
121
- * 不加载 WebAV:`engine` 子入口需要由调用方按需 `await import()`,因为 opfs-tools
23
+ * 不加载 WebAV:引擎模块在首次导入素材 / 导出时才动态加载 —— opfs-tools
122
24
  * 在模块加载期就会访问 `navigator.storage`,非 Chromium 环境会直接抛错。
123
25
  */
124
26
  export declare function createEditor(options?: EditorOptions): CutEditorHandle;
@@ -36,6 +36,10 @@ export interface EditorState {
36
36
  resetProject: () => void;
37
37
  renameProject: (name: string) => void;
38
38
  setProjectSize: (width: number, height: number, mode?: 'scale' | 'keep') => void;
39
+ /** 运行期改工程 id(宿主把临时工程归入某项目时用) */
40
+ setProjectId: (id: string) => void;
41
+ /** 整体替换「缺失素材」列表(importJson 补齐二进制后回写) */
42
+ setMissingAssetIds: (ids: string[]) => void;
39
43
  addAsset: (asset: MediaAsset) => void;
40
44
  clearMissing: (assetId: string) => void;
41
45
  replaceAsset: (assetId: string, next: MediaAsset) => void;
@@ -0,0 +1,256 @@
1
+ /**
2
+ * 编辑器句柄的公开契约(纯类型)。
3
+ *
4
+ * 本文件只含 `import type`,因此可以被根入口安全导出:不会把 WebAV /
5
+ * 运行时拉进 `@cut-workbench/core` 的模块图,Node 环境 import 依旧无副作用。
6
+ */
7
+ import type { StoreApi } from 'zustand/vanilla';
8
+ import type { MediaAsset, Project, SubtitleStyle, TextStyle, TimelineClip, Track } from '../types';
9
+ import type { SerializedProject } from '../project/schema';
10
+ import type { StoragePort } from '../project/storage';
11
+ import type { HostBridge, SaveFileResult } from '../ports/host-bridge';
12
+ import type { CapabilityReport } from '../capability';
13
+ import type { ExportHandle, ExportRequest, ExportResult } from '../export/types';
14
+ import type { EditorState } from './editor-store';
15
+ import type { HistoryStore } from './history-store';
16
+ import type { EventEmitterView } from './event-bus';
17
+ import type { EditorRuntime } from './runtime';
18
+ /** 对外事件表。新增事件时必须同步更新此接口。 */
19
+ export interface CutEventMap {
20
+ ready: {
21
+ editorId: string;
22
+ };
23
+ 'project:changed': {
24
+ project: Project;
25
+ dirty: boolean;
26
+ };
27
+ 'assets:changed': {
28
+ assets: MediaAsset[];
29
+ };
30
+ 'selection:changed': {
31
+ clipIds: string[];
32
+ assetId: string | null;
33
+ };
34
+ 'playhead:changed': {
35
+ us: number;
36
+ playing: boolean;
37
+ };
38
+ 'history:changed': {
39
+ canUndo: boolean;
40
+ canRedo: boolean;
41
+ };
42
+ /** 工程结构变化(dirty 由 false → true 或反向) */
43
+ 'project:dirty': {
44
+ dirty: boolean;
45
+ };
46
+ /** 保存完成(成功或失败);`path` 是宿主存储给出的位置(若有) */
47
+ 'project:saved': {
48
+ path?: string;
49
+ ok: boolean;
50
+ error?: string;
51
+ };
52
+ /** 工程载入完成;`missing` 是缺少二进制源的素材数 */
53
+ 'project:loaded': {
54
+ project: Project;
55
+ assetCount: number;
56
+ missing: number;
57
+ };
58
+ 'export:start': {
59
+ totalUs: number;
60
+ settings: import('../types').ExportSettings;
61
+ };
62
+ /** 0~1 */
63
+ 'export:progress': {
64
+ progress: number;
65
+ };
66
+ /**
67
+ * 导出完成。
68
+ *
69
+ * `result` 是完整结果(宿主入库需要时长 / 尺寸 / 码率)。
70
+ * 顶层 `blob` / `filename` 是为兼容既有订阅方保留的**便捷副本**,会被新代码忽略。
71
+ */
72
+ 'export:done': {
73
+ result: ExportResult;
74
+ blob: Blob;
75
+ filename: string;
76
+ };
77
+ 'export:error': {
78
+ message: string;
79
+ error: unknown;
80
+ };
81
+ error: {
82
+ scope: string;
83
+ error: unknown;
84
+ };
85
+ dispose: Record<string, never>;
86
+ }
87
+ /** 自动保存:结构性变更后节流写入 */
88
+ export interface AutoSaveOptions {
89
+ storage: StoragePort;
90
+ namespace: string;
91
+ /** 节流窗口,默认 2000ms */
92
+ intervalMs?: number;
93
+ }
94
+ export interface EditorOptions {
95
+ /** 宿主桥;不传则使用浏览器内置实现 */
96
+ bridge?: HostBridge;
97
+ /** 工程持久化端口;不传则用内存实现(不落盘) */
98
+ storage?: StoragePort;
99
+ /** 持久化命名空间(多项目 / 多实例隔离) */
100
+ namespace?: string;
101
+ /** 直接给一个完整工程 */
102
+ project?: Project;
103
+ /**
104
+ * 覆盖空工程的默认 id。宿主可用自己的项目 id,实现「一个项目一个剪辑工程」。
105
+ *
106
+ * 传了 `project` 时本字段被忽略(以工程自身的 id 为准)。
107
+ */
108
+ projectId?: string;
109
+ /** 初始素材元数据 */
110
+ assets?: MediaAsset[];
111
+ /** 自动保存;不传则不自动落盘 */
112
+ autoSave?: AutoSaveOptions;
113
+ }
114
+ export interface AddFilesOptions {
115
+ /** 导入成功后选中第一个素材;默认 true。批量预导入传 false,避免抢焦点 */
116
+ select?: boolean;
117
+ /** 逐文件进度:index 为入参数组下标,progress 为 0~1 */
118
+ onProgress?: (index: number, progress: number) => void;
119
+ /**
120
+ * 由宿主指定素材 id。返回 falsy 则回退到库生成的 `asset_xxx`。
121
+ * 用于宿主把自己的素材记录 id 作为 MediaAsset.id,实现「工程 ↔ 宿主库」一一对应。
122
+ */
123
+ idFor?: (file: File, index: number) => string | null | undefined;
124
+ /** 与缺失源文件的素材同名同类型时重连而非新增;默认 true */
125
+ reconnect?: boolean;
126
+ }
127
+ export interface AddFilesResult {
128
+ imported: MediaAsset[];
129
+ failures: Array<{
130
+ name: string;
131
+ error: string;
132
+ }>;
133
+ /** 其中走重连的数量 */
134
+ reconnected: number;
135
+ }
136
+ export interface AddTextAssetParams {
137
+ text: string;
138
+ style?: Partial<TextStyle>;
139
+ /** 素材默认时长(微秒);缺省 5s */
140
+ durationUs?: number;
141
+ }
142
+ export interface AddSubtitleAssetParams {
143
+ srt: string;
144
+ style?: Partial<SubtitleStyle>;
145
+ /** 素材默认时长(微秒);缺省取 SRT 最后一条字幕的结束时间 */
146
+ durationUs?: number;
147
+ }
148
+ export interface ImportJsonOptions {
149
+ /**
150
+ * 由宿主补齐二进制。返回 null 表示该素材缺失,进入 missingAssetIds。
151
+ */
152
+ resolveFile?: (asset: MediaAsset) => Promise<File | null>;
153
+ }
154
+ export interface ImportJsonResult {
155
+ missingAssetIds: string[];
156
+ }
157
+ export interface SaveAsResult {
158
+ ok: boolean;
159
+ path?: string;
160
+ error?: string;
161
+ }
162
+ /**
163
+ * 时间轴命令门面。
164
+ *
165
+ * 与 `CutEditorHandle` 上的同名方法完全等价,供宿主在「注入命令对象」的
166
+ * 场景(例如自己的快捷键表)里使用。
167
+ */
168
+ export interface EditorCommands {
169
+ updateClip: (clipId: string, patch: Partial<TimelineClip>, recordHistory?: boolean, mergeKey?: string) => void;
170
+ moveClip: (clipId: string, trackId: string, startUs: number, recordHistory?: boolean) => void;
171
+ trimClip: (clipId: string, edge: 'start' | 'end', deltaUs: number, recordHistory?: boolean) => void;
172
+ splitClipsAt: (atUs: number, clipIds?: string[]) => string[];
173
+ duplicateSelection: (offsetUs: number) => Array<{
174
+ sourceId: string;
175
+ newId: string;
176
+ }>;
177
+ addTrack: (kind: Track['kind']) => string;
178
+ removeTrack: (trackId: string) => void;
179
+ updateTrack: (trackId: string, patch: Partial<Track>) => void;
180
+ }
181
+ export interface CutEditorHandle {
182
+ readonly id: string;
183
+ readonly store: StoreApi<EditorState>;
184
+ readonly history: HistoryStore;
185
+ readonly runtime: EditorRuntime;
186
+ readonly events: EventEmitterView<CutEventMap>;
187
+ readonly bridge: HostBridge;
188
+ readonly storage: StoragePort;
189
+ /** 时间轴命令门面(与下方同名方法等价) */
190
+ readonly commands: EditorCommands;
191
+ getProject(): Project;
192
+ getProjectId(): string;
193
+ /** 运行期改 id;会同步 emit `project:changed` */
194
+ setProjectId(id: string): void;
195
+ renameProject(name: string): void;
196
+ setProjectSize(width: number, height: number, mode?: 'scale' | 'keep'): void;
197
+ getAssets(): MediaAsset[];
198
+ getState(): EditorState;
199
+ setProject(project: Project, assets: MediaAsset[], missingAssetIds?: string[]): void;
200
+ serialize(): SerializedProject;
201
+ /** `serialize()` 的别名,语义更直白 */
202
+ exportJson(): SerializedProject;
203
+ /** 从 JSON / Project 恢复(不补齐二进制,缺源素材进入 missingAssetIds) */
204
+ load(input: SerializedProject | Project): Promise<void>;
205
+ /**
206
+ * 从 JSON 恢复,并由宿主补齐二进制。
207
+ * `resolveFile(asset)` 返回 null 表示该素材缺失,进入 missingAssetIds。
208
+ */
209
+ importJson(data: SerializedProject, options?: ImportJsonOptions): Promise<ImportJsonResult>;
210
+ save(): Promise<SaveAsResult>;
211
+ /** 换一个存储端口 / 命名空间保存,并把它设为后续 `save()` 的目标 */
212
+ saveAs(storage: StoragePort, namespace: string): Promise<SaveAsResult>;
213
+ isDirty(): boolean;
214
+ isEmpty(): boolean;
215
+ addFiles(files: File[], options?: AddFilesOptions): Promise<AddFilesResult>;
216
+ addTextAsset(params: AddTextAssetParams): Promise<MediaAsset>;
217
+ addSubtitleAsset(params: AddSubtitleAssetParams): Promise<MediaAsset>;
218
+ /** 为缺失源文件的素材补上文件(重新打开工程、按 id 找回本地文件后调用) */
219
+ reconnectAsset(assetId: string, file: File): Promise<void>;
220
+ getMissingAssetIds(): string[];
221
+ removeAsset(assetId: string): void;
222
+ play(): void;
223
+ pause(): void;
224
+ toggle(): void;
225
+ seek(us: number): void;
226
+ getPlayhead(): number;
227
+ getDuration(): number;
228
+ isPlaying(): boolean;
229
+ undo(): void;
230
+ redo(): void;
231
+ canUndo(): boolean;
232
+ canRedo(): boolean;
233
+ addClip(clip: TimelineClip): void;
234
+ removeClips(ids: string[]): void;
235
+ selectClips(ids: string[]): void;
236
+ getSelectedClipIds(): string[];
237
+ setSelection(clipIds: string[], assetId?: string | null): void;
238
+ updateClip(clipId: string, patch: Partial<TimelineClip>, recordHistory?: boolean, mergeKey?: string): void;
239
+ moveClip(clipId: string, trackId: string, startUs: number, recordHistory?: boolean): void;
240
+ trimClip(clipId: string, edge: 'start' | 'end', deltaUs: number, recordHistory?: boolean): void;
241
+ splitClipsAt(atUs: number, clipIds?: string[]): string[];
242
+ duplicateSelection(offsetUs: number): Array<{
243
+ sourceId: string;
244
+ newId: string;
245
+ }>;
246
+ addTrack(kind: Track['kind']): string;
247
+ removeTrack(trackId: string): void;
248
+ updateTrack(trackId: string, patch: Partial<Track>): void;
249
+ /** 启动导出;内部优先 Worker,不可用时回退主线程 */
250
+ export(request?: ExportRequest): ExportHandle;
251
+ /** 把导出结果交给宿主落盘(`bridge.files.saveBlob`) */
252
+ saveExport(result: ExportResult, suggestedName?: string): Promise<SaveFileResult>;
253
+ /** 能力探测结果(首次调用后缓存) */
254
+ getCapabilities(): Promise<CapabilityReport>;
255
+ dispose(): Promise<void>;
256
+ }