@cut-workbench/core 0.3.0 → 0.4.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/dist/chunks/{canvas-size-iWL8zZPl.js → canvas-size-CM2oKApi.js} +3 -144
- package/dist/chunks/{canvas-size-iWL8zZPl.js.map → canvas-size-CM2oKApi.js.map} +1 -1
- package/dist/chunks/{canvas-size-IzoEg2cq.cjs → canvas-size-Ct61OYjW.cjs} +26 -287
- package/dist/chunks/canvas-size-Ct61OYjW.cjs.map +1 -0
- package/dist/chunks/import-pipeline-A8KkqtZT.cjs +610 -0
- package/dist/chunks/import-pipeline-A8KkqtZT.cjs.map +1 -0
- package/dist/chunks/import-pipeline-DLIdgrIb.js +521 -0
- package/dist/chunks/import-pipeline-DLIdgrIb.js.map +1 -0
- package/dist/chunks/rolldown-runtime-B2SfzNBj.cjs +17 -0
- package/dist/chunks/rolldown-runtime-D8jQpDG7.js +12 -0
- package/dist/chunks/{snap-Dc022smD.js → snap-CBV9G4xq.js} +2 -2
- package/dist/chunks/{snap-Dc022smD.js.map → snap-CBV9G4xq.js.map} +1 -1
- package/dist/chunks/{snap-CeYuTN6d.cjs → snap-DyIuO2hU.cjs} +3 -3
- package/dist/chunks/{snap-CeYuTN6d.cjs.map → snap-DyIuO2hU.cjs.map} +1 -1
- package/dist/chunks/srt-D539yJ-n.js +51 -0
- package/dist/chunks/srt-D539yJ-n.js.map +1 -0
- package/dist/chunks/srt-DaL36mXN.cjs +73 -0
- package/dist/chunks/srt-DaL36mXN.cjs.map +1 -0
- package/dist/chunks/time-C_gvkVl1.js +145 -0
- package/dist/chunks/time-C_gvkVl1.js.map +1 -0
- package/dist/chunks/time-CnMfSVYX.cjs +264 -0
- package/dist/chunks/time-CnMfSVYX.cjs.map +1 -0
- package/dist/chunks/{waveform-CWU8Fa-n.js → waveform-DJkRtMct.js} +82 -12
- package/dist/chunks/waveform-DJkRtMct.js.map +1 -0
- package/dist/chunks/{waveform-Bhvv2t7-.cjs → waveform-Ie-G5Lle.cjs} +121 -21
- package/dist/chunks/waveform-Ie-G5Lle.cjs.map +1 -0
- package/dist/editor/create-editor.d.ts +8 -106
- package/dist/editor/editor-store.d.ts +4 -0
- package/dist/editor/handle-types.d.ts +256 -0
- package/dist/engine/index.cjs +961 -823
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.ts +3 -0
- package/dist/engine/index.js +935 -803
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/webav/export-runner.d.ts +61 -0
- package/dist/engine/webav/import-files.d.ts +43 -0
- package/dist/engine/webav/import-pipeline.d.ts +13 -1
- package/dist/errors.d.ts +12 -0
- package/dist/export/types.d.ts +49 -0
- package/dist/index.cjs +75 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +46 -43
- package/dist/index.js.map +1 -1
- package/dist/ports/host-bridge.d.ts +7 -0
- package/dist/project/file-storage.d.ts +23 -0
- package/dist/timeline/index.cjs +17 -16
- package/dist/timeline/index.js +3 -2
- package/package.json +78 -77
- package/dist/chunks/canvas-size-IzoEg2cq.cjs.map +0 -1
- package/dist/chunks/waveform-Bhvv2t7-.cjs.map +0 -1
- package/dist/chunks/waveform-CWU8Fa-n.js.map +0 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 导出编排(core 版本)。
|
|
3
|
+
*
|
|
4
|
+
* 分层:本模块允许 import `@webav/*`(位于 engine/webav 下),但**不**依赖 React,
|
|
5
|
+
* 也不做任何 UI。上层(`editor.export()` 与 `<CutWorkbench>` 的导出对话框)
|
|
6
|
+
* 都消费同一套逻辑,避免「UI 里能导出、命令式 API 不能导出」的分裂。
|
|
7
|
+
*
|
|
8
|
+
* 两条路径:
|
|
9
|
+
* - 默认走 Worker(不阻塞 UI);
|
|
10
|
+
* - 含文字片段(依赖 DOM)或 Worker 不可用时回退主线程;
|
|
11
|
+
* - 含音频 / 视频音轨时,主线程会先解码 PCM 再传给 Worker
|
|
12
|
+
* (Worker 内没有 AudioContext,AudioClip 无法自行解码)。
|
|
13
|
+
*/
|
|
14
|
+
import type { MediaAsset, Project } from '../../types';
|
|
15
|
+
import type { ExportSettings } from '../../types';
|
|
16
|
+
import type { ExportResult } from '../../export/types';
|
|
17
|
+
import type { ClipRegistry } from './clip-registry';
|
|
18
|
+
import type { WorkerAssetPayload, WorkerTimelinePayload } from './types';
|
|
19
|
+
export interface ExportPlanOptions {
|
|
20
|
+
registry: ClipRegistry;
|
|
21
|
+
/**
|
|
22
|
+
* Worker 工厂;返回 null 表示不可用,直接走主线程。
|
|
23
|
+
* 不传时使用 `setDefaultExportWorkerFactory` 注册的默认实现(若有)。
|
|
24
|
+
*/
|
|
25
|
+
createWorker?: () => Worker | null;
|
|
26
|
+
project: Project;
|
|
27
|
+
assets: MediaAsset[];
|
|
28
|
+
settings: ExportSettings;
|
|
29
|
+
/** 导出上限(微秒),默认全片 */
|
|
30
|
+
maxTimeUs?: number;
|
|
31
|
+
/** 输出 Blob 的 MIME,默认 video/mp4 */
|
|
32
|
+
mimeType?: string;
|
|
33
|
+
filename?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface CoreExportHandle {
|
|
36
|
+
promise: Promise<ExportResult>;
|
|
37
|
+
cancel: () => void;
|
|
38
|
+
}
|
|
39
|
+
export declare function setDefaultExportWorkerFactory(factory: (() => Worker | null) | null): void;
|
|
40
|
+
/** 判断是否可以用 Worker 导出(文字素材依赖 DOM) */
|
|
41
|
+
export declare function canUseWorker(project: Project, assets: MediaAsset[]): boolean;
|
|
42
|
+
/** 参与导出的片段最大结束时间(跳过隐藏轨道) */
|
|
43
|
+
export declare function computeExportDuration(project: Project, maxTimeUs?: number): number;
|
|
44
|
+
/**
|
|
45
|
+
* 构建 Worker 载荷。
|
|
46
|
+
*
|
|
47
|
+
* 音频素材需要主线程预解码 PCM(Worker 无 AudioContext)。为避免无谓解码,
|
|
48
|
+
* 只有「音频轨素材」或「含音轨且会被播放的视频」才解码。
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildWorkerPayload(registry: ClipRegistry, project: Project, assets: MediaAsset[], settings: ExportSettings, maxTimeUs?: number): Promise<{
|
|
51
|
+
assets: WorkerAssetPayload[];
|
|
52
|
+
timeline: WorkerTimelinePayload;
|
|
53
|
+
missing: string[];
|
|
54
|
+
}>;
|
|
55
|
+
/**
|
|
56
|
+
* 启动导出。
|
|
57
|
+
*
|
|
58
|
+
* 优先走 Worker(不阻塞 UI);含文字片段或 Worker 不可用 / 失败时回退主线程。
|
|
59
|
+
* `cancel()` 后 promise 以 `ExportCancelledError` reject。
|
|
60
|
+
*/
|
|
61
|
+
export declare function startExport(options: ExportPlanOptions, onProgress?: (progress: number) => void): CoreExportHandle;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 批量导入素材(core 的唯一实现)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么放在 core:导入语义(重连缺失素材、id 注入、失败汇总、串行解码)
|
|
5
|
+
* 被三处调用 —— `<CutWorkbench>` 的 `useMediaImport`、`initCutWorkbench().addFiles`、
|
|
6
|
+
* 以及 `editor.addFiles`。过去 workbench 里另有一份实现,行为与 init 那份不一致
|
|
7
|
+
* (init 不重连、不报进度),宿主换个入口就得到不同结果。
|
|
8
|
+
*
|
|
9
|
+
* 本文件位于 `engine/webav` 下,因此允许 import `@webav/*`(经 importFile 间接)。
|
|
10
|
+
* 编辑器句柄会**动态**加载它,宿主不需要自己写动态 import。
|
|
11
|
+
*/
|
|
12
|
+
import type { StoreApi } from 'zustand/vanilla';
|
|
13
|
+
import type { MediaAsset } from '../../types';
|
|
14
|
+
import type { EditorState } from '../../editor/editor-store';
|
|
15
|
+
import type { ClipRegistry } from './clip-registry';
|
|
16
|
+
export interface ImportFilesOptions {
|
|
17
|
+
/** 导入成功后选中第一个素材;默认 true */
|
|
18
|
+
select?: boolean;
|
|
19
|
+
/** 逐文件进度:index 为入参数组下标,progress 为 0~1 */
|
|
20
|
+
onProgress?: (index: number, progress: number) => void;
|
|
21
|
+
/** 由宿主指定素材 id;返回 falsy 则回退到库生成的 `asset_xxx` */
|
|
22
|
+
idFor?: (file: File, index: number) => string | null | undefined;
|
|
23
|
+
/** 与缺失源文件的素材同名同类型时重连而非新增;默认 true */
|
|
24
|
+
reconnect?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface ImportFilesResult {
|
|
27
|
+
imported: MediaAsset[];
|
|
28
|
+
failures: Array<{
|
|
29
|
+
name: string;
|
|
30
|
+
error: string;
|
|
31
|
+
}>;
|
|
32
|
+
/** 其中走重连的数量 */
|
|
33
|
+
reconnected: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 批量导入素材。
|
|
37
|
+
*
|
|
38
|
+
* - 串行处理,避免同时开辟多个解码器把内存打爆;
|
|
39
|
+
* - 单个文件失败只影响自身,最后汇总返回;
|
|
40
|
+
* - 进度由调用方通过 `onProgress` 展示(不写 store,避免高频全局重渲染);
|
|
41
|
+
* - 与「缺失源文件」的素材同名同类型时重连,复用原 assetId。
|
|
42
|
+
*/
|
|
43
|
+
export declare function importFiles(registry: ClipRegistry, store: StoreApi<EditorState>, files: File[], options?: ImportFilesOptions): Promise<ImportFilesResult>;
|
|
@@ -16,13 +16,21 @@ export interface ImportedAsset {
|
|
|
16
16
|
/** 母版 clip(重连素材时用于替换旧的母版) */
|
|
17
17
|
master?: IClip;
|
|
18
18
|
}
|
|
19
|
+
export interface ImportFileOptions {
|
|
20
|
+
/**
|
|
21
|
+
* 指定素材 id(宿主把自己的素材记录 id 作为 `MediaAsset.id`)。
|
|
22
|
+
*
|
|
23
|
+
* 传了就必须保证会话内唯一:重复注册会覆盖 registry 里的旧素材。
|
|
24
|
+
*/
|
|
25
|
+
id?: string;
|
|
26
|
+
}
|
|
19
27
|
/**
|
|
20
28
|
* 导入单个文件,生成素材记录。
|
|
21
29
|
*
|
|
22
30
|
* @param onProgress 0~1 进度(用于卡片骨架/进度条)
|
|
23
31
|
* @throws UnsupportedFileError 类型不支持时
|
|
24
32
|
*/
|
|
25
|
-
export declare function importFile(registry: ClipRegistry, file: File, onProgress?: (progress: number) => void): Promise<ImportedAsset>;
|
|
33
|
+
export declare function importFile(registry: ClipRegistry, file: File, onProgress?: (progress: number) => void, options?: ImportFileOptions): Promise<ImportedAsset>;
|
|
26
34
|
/** 新建文字素材(非文件来源,无需二进制) */
|
|
27
35
|
export declare function createTextAsset(params: {
|
|
28
36
|
registry: ClipRegistry;
|
|
@@ -31,6 +39,8 @@ export declare function createTextAsset(params: {
|
|
|
31
39
|
width: number;
|
|
32
40
|
height: number;
|
|
33
41
|
durationUs?: number;
|
|
42
|
+
/** 指定素材 id;不传则生成 `asset_xxx` */
|
|
43
|
+
id?: string;
|
|
34
44
|
}): ImportedAsset;
|
|
35
45
|
/**
|
|
36
46
|
* 解码素材的完整 PCM(48kHz 双声道),供 Worker 导出使用。
|
|
@@ -52,5 +62,7 @@ export declare function createSubtitleAsset(params: {
|
|
|
52
62
|
height: number;
|
|
53
63
|
durationUs?: number;
|
|
54
64
|
style?: Partial<import('../../types').SubtitleStyle>;
|
|
65
|
+
/** 指定素材 id;不传则生成 `asset_xxx` */
|
|
66
|
+
id?: string;
|
|
55
67
|
}): ImportedAsset;
|
|
56
68
|
export { UnsupportedFileError };
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 对外错误类型。
|
|
3
|
+
*
|
|
4
|
+
* 放在根入口(不依赖 WebAV / React),宿主可以 `instanceof` 判断后决定
|
|
5
|
+
* 是否弹错误框 —— 用户主动取消导出不是错误,不该被当成失败提示。
|
|
6
|
+
*/
|
|
7
|
+
/** 导出被 `cancel()` 取消。 */
|
|
8
|
+
export declare class ExportCancelledError extends Error {
|
|
9
|
+
constructor(message?: string);
|
|
10
|
+
}
|
|
11
|
+
/** 判断任意异常是否是「用户取消导出」。 */
|
|
12
|
+
export declare function isExportCancelled(error: unknown): error is ExportCancelledError;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 导出能力的公开契约(纯类型 + 纯函数,不依赖 WebAV / React)。
|
|
3
|
+
*
|
|
4
|
+
* 放在根入口可达的位置,宿主可以只依赖 `@cut-workbench/core` 就能标注
|
|
5
|
+
* 导出请求与结果,不必为了类型去加载引擎子入口。
|
|
6
|
+
*/
|
|
7
|
+
import type { ExportSettings, Project } from '../types';
|
|
8
|
+
export interface ExportRequest {
|
|
9
|
+
/** 输出参数;缺省为 1080p / 30fps / 8Mbps */
|
|
10
|
+
settings?: Partial<ExportSettings>;
|
|
11
|
+
/** 输出 blob 的 MIME,默认 `video/mp4` */
|
|
12
|
+
mimeType?: string;
|
|
13
|
+
/** 输出文件名(不含路径),默认按时间生成 */
|
|
14
|
+
filename?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ExportResult {
|
|
17
|
+
blob: Blob;
|
|
18
|
+
filename: string;
|
|
19
|
+
mimeType: string;
|
|
20
|
+
/** 成片时长(微秒)。宿主入库需要 */
|
|
21
|
+
durationUs: number;
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
fps: number;
|
|
25
|
+
bitrate: number;
|
|
26
|
+
elapsedMs: number;
|
|
27
|
+
/** 实际执行路径:Worker 或主线程回退(诊断用,宿主可忽略) */
|
|
28
|
+
mode: 'worker' | 'main';
|
|
29
|
+
/** 被跳过的片段(缺源文件 / 类型不支持) */
|
|
30
|
+
skipped: Array<{
|
|
31
|
+
clipId: string;
|
|
32
|
+
reason: string;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export interface ExportHandle {
|
|
36
|
+
promise: Promise<ExportResult>;
|
|
37
|
+
cancel(): void;
|
|
38
|
+
}
|
|
39
|
+
/** 导出默认值:1080p / 30fps / 8Mbps / 含音轨 */
|
|
40
|
+
export declare const DEFAULT_EXPORT_SETTINGS: ExportSettings;
|
|
41
|
+
/**
|
|
42
|
+
* 把宿主传入的局部设置补全为完整设置。
|
|
43
|
+
*
|
|
44
|
+
* 只给了 `preset` 时按预设推导尺寸(`source` 取工程尺寸并向下对齐到偶数),
|
|
45
|
+
* 显式给了 `width` / `height` 则以显式值为准。
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveExportSettings(project: Project, partial?: Partial<ExportSettings>): ExportSettings;
|
|
48
|
+
/** 默认文件名(与编辑器无关,宿主可覆盖) */
|
|
49
|
+
export declare function createExportFilename(date?: Date): string;
|
package/dist/index.cjs
CHANGED
|
@@ -1,49 +1,52 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
2
|
+
const require_time = require("./chunks/time-CnMfSVYX.cjs");
|
|
3
|
+
const require_canvas_size = require("./chunks/canvas-size-Ct61OYjW.cjs");
|
|
4
|
+
const require_snap = require("./chunks/snap-DyIuO2hU.cjs");
|
|
5
|
+
const require_waveform = require("./chunks/waveform-Ie-G5Lle.cjs");
|
|
6
|
+
const require_srt = require("./chunks/srt-DaL36mXN.cjs");
|
|
5
7
|
const require_file_reader = require("./chunks/file-reader-DfMEluGr.cjs");
|
|
6
|
-
// src/
|
|
7
|
-
var
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// src/project/file-storage.ts
|
|
9
|
+
var JSON_EXT = ".json";
|
|
10
|
+
/** namespace → 安全文件名(不含扩展名) */
|
|
11
|
+
function toStorageFileName(namespace) {
|
|
12
|
+
const safe = namespace.replace(/[\\/:*?"<>|\u0000-\u001f]/g, "_").replace(/^\.+/, "_").trim();
|
|
13
|
+
return (safe.length > 0 ? safe : "default") + JSON_EXT;
|
|
10
14
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const cues = [];
|
|
15
|
-
for (const block of blocks) {
|
|
16
|
-
const lines = block.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
17
|
-
if (lines.length === 0) continue;
|
|
18
|
-
const timeLineIndex = lines.findIndex((l) => l.includes("-->"));
|
|
19
|
-
if (timeLineIndex < 0) continue;
|
|
20
|
-
const timeLine = lines[timeLineIndex] ?? "";
|
|
21
|
-
TIME_RE.lastIndex = 0;
|
|
22
|
-
const matches = Array.from(timeLine.matchAll(TIME_RE));
|
|
23
|
-
if (matches.length < 2) continue;
|
|
24
|
-
const [startMatch, endMatch] = matches;
|
|
25
|
-
if (startMatch == null || endMatch == null) continue;
|
|
26
|
-
const startMs = toMs(startMatch[1] ?? "0", startMatch[2] ?? "0", startMatch[3] ?? "0", startMatch[4] ?? "0");
|
|
27
|
-
const endMs = toMs(endMatch[1] ?? "0", endMatch[2] ?? "0", endMatch[3] ?? "0", endMatch[4] ?? "0");
|
|
28
|
-
if (endMs <= startMs) continue;
|
|
29
|
-
cues.push({
|
|
30
|
-
index: cues.length + 1,
|
|
31
|
-
startMs,
|
|
32
|
-
endMs,
|
|
33
|
-
text: lines.slice(timeLineIndex + 1).join("\n")
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
return cues;
|
|
37
|
-
}
|
|
38
|
-
/** 字幕总时长(微秒)。用于给字幕素材一个有限的 deration */
|
|
39
|
-
function parseSrtDurationUs(content) {
|
|
40
|
-
const cues = parseSrt(content);
|
|
41
|
-
const last = cues[cues.length - 1];
|
|
42
|
-
return last == null ? 0 : Math.round(last.endMs * 1e3);
|
|
15
|
+
function joinPath(dir, name) {
|
|
16
|
+
const separator = dir.includes("\\") && !dir.includes("/") ? "\\" : "/";
|
|
17
|
+
return dir.endsWith("/") || dir.endsWith("\\") ? `${dir}${name}` : `${dir}${separator}${name}`;
|
|
43
18
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return
|
|
19
|
+
function createFileStorage(dir, io) {
|
|
20
|
+
const pathFor = (namespace) => joinPath(dir, toStorageFileName(namespace));
|
|
21
|
+
return {
|
|
22
|
+
async load(namespace) {
|
|
23
|
+
const text = await io.readText(pathFor(namespace));
|
|
24
|
+
if (text == null || text.trim().length === 0) return null;
|
|
25
|
+
return JSON.parse(text);
|
|
26
|
+
},
|
|
27
|
+
async save(namespace, data) {
|
|
28
|
+
const path = pathFor(namespace);
|
|
29
|
+
try {
|
|
30
|
+
await io.writeText(path, JSON.stringify(data, null, 2));
|
|
31
|
+
return {
|
|
32
|
+
ok: true,
|
|
33
|
+
path
|
|
34
|
+
};
|
|
35
|
+
} catch (err) {
|
|
36
|
+
return {
|
|
37
|
+
ok: false,
|
|
38
|
+
path,
|
|
39
|
+
error: err instanceof Error ? err.message : String(err)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
async remove(namespace) {
|
|
44
|
+
await io.remove(pathFor(namespace));
|
|
45
|
+
},
|
|
46
|
+
async list() {
|
|
47
|
+
return (await io.list(dir)).filter((name) => name.toLowerCase().endsWith(JSON_EXT)).map((name) => name.slice(0, -5)).sort();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
47
50
|
}
|
|
48
51
|
// src/media/opfs.ts
|
|
49
52
|
/**
|
|
@@ -115,25 +118,27 @@ async function estimateQuota() {
|
|
|
115
118
|
}
|
|
116
119
|
}
|
|
117
120
|
exports.ASPECT_PRESETS = require_canvas_size.ASPECT_PRESETS;
|
|
118
|
-
exports.DEFAULT_CLIP_VOLUME =
|
|
119
|
-
exports.
|
|
121
|
+
exports.DEFAULT_CLIP_VOLUME = require_time.DEFAULT_CLIP_VOLUME;
|
|
122
|
+
exports.DEFAULT_EXPORT_SETTINGS = require_waveform.DEFAULT_EXPORT_SETTINGS;
|
|
123
|
+
exports.DEFAULT_STILL_DURATION_US = require_time.DEFAULT_STILL_DURATION_US;
|
|
120
124
|
exports.EMPTY_SNAP = require_snap.EMPTY_SNAP;
|
|
121
125
|
exports.ENGINE_DEPENDENCIES_INSTALL_COMMAND = require_waveform.ENGINE_DEPENDENCIES_INSTALL_COMMAND;
|
|
122
|
-
exports.
|
|
123
|
-
exports.
|
|
126
|
+
exports.ExportCancelledError = require_waveform.ExportCancelledError;
|
|
127
|
+
exports.HISTORY_MERGE_WINDOW_MS = require_time.HISTORY_MERGE_WINDOW_MS;
|
|
128
|
+
exports.MAX_HISTORY_STEPS = require_time.MAX_HISTORY_STEPS;
|
|
124
129
|
exports.MIN_TRANSFORM_SIZE = require_canvas_size.MIN_TRANSFORM_SIZE;
|
|
125
|
-
exports.PROJECT_SCHEMA_VERSION =
|
|
130
|
+
exports.PROJECT_SCHEMA_VERSION = require_time.PROJECT_SCHEMA_VERSION;
|
|
126
131
|
exports.ProjectSchemaError = require_waveform.ProjectSchemaError;
|
|
127
132
|
exports.RESIZE_HANDLES = require_canvas_size.RESIZE_HANDLES;
|
|
128
|
-
exports.US_PER_MS =
|
|
129
|
-
exports.US_PER_SECOND =
|
|
133
|
+
exports.US_PER_MS = require_time.US_PER_MS;
|
|
134
|
+
exports.US_PER_SECOND = require_time.US_PER_SECOND;
|
|
130
135
|
exports.UnsupportedFileError = require_file_reader.UnsupportedFileError;
|
|
131
|
-
exports.asPx =
|
|
132
|
-
exports.asUs =
|
|
136
|
+
exports.asPx = require_time.asPx;
|
|
137
|
+
exports.asUs = require_time.asUs;
|
|
133
138
|
exports.bufferToStream = require_file_reader.bufferToStream;
|
|
134
|
-
exports.buildTicks =
|
|
139
|
+
exports.buildTicks = require_time.buildTicks;
|
|
135
140
|
exports.buildWaveformPath = require_waveform.buildWaveformPath;
|
|
136
|
-
exports.clamp =
|
|
141
|
+
exports.clamp = require_time.clamp;
|
|
137
142
|
exports.clampStillDuration = require_canvas_size.clampStillDuration;
|
|
138
143
|
exports.clearTempFiles = clearTempFiles;
|
|
139
144
|
exports.compactTrackData = require_canvas_size.compactTrackData;
|
|
@@ -144,8 +149,10 @@ exports.createClip = require_canvas_size.createClip;
|
|
|
144
149
|
exports.createEditorStore = require_waveform.createEditorStore;
|
|
145
150
|
exports.createEmptyProject = require_canvas_size.createEmptyProject;
|
|
146
151
|
exports.createEventBus = require_waveform.createEventBus;
|
|
152
|
+
exports.createExportFilename = require_waveform.createExportFilename;
|
|
153
|
+
exports.createFileStorage = createFileStorage;
|
|
147
154
|
exports.createHistoryStore = require_waveform.createHistoryStore;
|
|
148
|
-
exports.createId =
|
|
155
|
+
exports.createId = require_time.createId;
|
|
149
156
|
exports.createIndexedDbStorage = require_waveform.createIndexedDbStorage;
|
|
150
157
|
exports.createMemoryStorage = require_waveform.createMemoryStorage;
|
|
151
158
|
exports.createTrack = require_canvas_size.createTrack;
|
|
@@ -161,8 +168,8 @@ exports.duplicateClipsData = require_canvas_size.duplicateClipsData;
|
|
|
161
168
|
exports.estimateQuota = estimateQuota;
|
|
162
169
|
exports.fileToStream = require_file_reader.fileToStream;
|
|
163
170
|
exports.findOverlaps = require_canvas_size.findOverlaps;
|
|
164
|
-
exports.formatDuration =
|
|
165
|
-
exports.formatTimecode =
|
|
171
|
+
exports.formatDuration = require_time.formatDuration;
|
|
172
|
+
exports.formatTimecode = require_time.formatTimecode;
|
|
166
173
|
exports.fromLocalPoint = require_canvas_size.fromLocalPoint;
|
|
167
174
|
exports.getClipEnd = require_canvas_size.getClipEnd;
|
|
168
175
|
exports.getProjectDuration = require_canvas_size.getProjectDuration;
|
|
@@ -174,19 +181,20 @@ exports.handleCursor = require_canvas_size.handleCursor;
|
|
|
174
181
|
exports.handleLocalOffset = require_canvas_size.handleLocalOffset;
|
|
175
182
|
exports.isCustomSize = require_canvas_size.isCustomSize;
|
|
176
183
|
exports.isEnvironmentUsable = require_waveform.isEnvironmentUsable;
|
|
184
|
+
exports.isExportCancelled = require_waveform.isExportCancelled;
|
|
177
185
|
exports.isTrackAudible = require_canvas_size.isTrackAudible;
|
|
178
|
-
exports.isValidSrt = isValidSrt;
|
|
186
|
+
exports.isValidSrt = require_srt.isValidSrt;
|
|
179
187
|
exports.moveClipData = require_canvas_size.moveClipData;
|
|
180
|
-
exports.msToUs =
|
|
188
|
+
exports.msToUs = require_time.msToUs;
|
|
181
189
|
exports.nearestSorted = require_snap.nearestSorted;
|
|
182
190
|
exports.normalizeAngle = require_canvas_size.normalizeAngle;
|
|
183
191
|
exports.normalizeSize = require_canvas_size.normalizeSize;
|
|
184
192
|
exports.normalizeWaveform = require_waveform.normalizeWaveform;
|
|
185
|
-
exports.parseSrt = parseSrt;
|
|
186
|
-
exports.parseSrtDurationUs = parseSrtDurationUs;
|
|
193
|
+
exports.parseSrt = require_srt.parseSrt;
|
|
194
|
+
exports.parseSrtDurationUs = require_srt.parseSrtDurationUs;
|
|
187
195
|
exports.pointInTransform = require_canvas_size.pointInTransform;
|
|
188
196
|
exports.probeOpfs = probeOpfs;
|
|
189
|
-
exports.pxToUs =
|
|
197
|
+
exports.pxToUs = require_time.pxToUs;
|
|
190
198
|
exports.rangesOverlap = require_canvas_size.rangesOverlap;
|
|
191
199
|
exports.ratioLabel = require_canvas_size.ratioLabel;
|
|
192
200
|
exports.readTempFile = readTempFile;
|
|
@@ -198,8 +206,9 @@ exports.removeClipsData = require_canvas_size.removeClipsData;
|
|
|
198
206
|
exports.removeTempFile = removeTempFile;
|
|
199
207
|
exports.reorderTracksData = require_canvas_size.reorderTracksData;
|
|
200
208
|
exports.resizeTransform = require_canvas_size.resizeTransform;
|
|
209
|
+
exports.resolveExportSettings = require_waveform.resolveExportSettings;
|
|
201
210
|
exports.sameTransform = require_canvas_size.sameTransform;
|
|
202
|
-
exports.secToUs =
|
|
211
|
+
exports.secToUs = require_time.secToUs;
|
|
203
212
|
exports.selectAssetById = require_waveform.selectAssetById;
|
|
204
213
|
exports.selectAssets = require_waveform.selectAssets;
|
|
205
214
|
exports.selectClipById = require_waveform.selectClipById;
|
|
@@ -213,10 +222,11 @@ exports.serializeProject = require_waveform.serializeProject;
|
|
|
213
222
|
exports.splitClipData = require_canvas_size.splitClipData;
|
|
214
223
|
exports.summarizeCapabilities = require_waveform.summarizeCapabilities;
|
|
215
224
|
exports.toLocalPoint = require_canvas_size.toLocalPoint;
|
|
225
|
+
exports.toStorageFileName = toStorageFileName;
|
|
216
226
|
exports.trimClipData = require_canvas_size.trimClipData;
|
|
217
|
-
exports.usToMs =
|
|
218
|
-
exports.usToPx =
|
|
219
|
-
exports.usToSec =
|
|
227
|
+
exports.usToMs = require_time.usToMs;
|
|
228
|
+
exports.usToPx = require_time.usToPx;
|
|
229
|
+
exports.usToSec = require_time.usToSec;
|
|
220
230
|
exports.writeTempFile = writeTempFile;
|
|
221
231
|
|
|
222
232
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../src/
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/project/file-storage.ts","../src/media/opfs.ts"],"sourcesContent":["/**\n * 文件式存储适配器。\n *\n * 宿主要把工程存成本地 JSON(Electron 主进程写盘)时,只需要提供四个文本\n * 操作,不必理解 `SerializedProject` 的字段。\n *\n * 映射规则:`<dir>/<namespace>.json`。namespace 做一次文件系统安全化:\n * 路径分隔符、Windows 保留字符与控制字符替换为 `_`,避免宿主传入\n * `projects/p_1` 这类含层级的值时写到目录之外。\n */\n\nimport type { SerializedProject } from './schema'\nimport type { StoragePort } from './storage'\n\nexport interface FileIoAdapter {\n /** 文件不存在时返回 null(不要抛错) */\n readText(path: string): Promise<string | null>\n writeText(path: string, content: string): Promise<void>\n /** 文件不存在时静默成功 */\n remove(path: string): Promise<void>\n /** 目录不存在时返回空数组 */\n list(dir: string): Promise<string[]>\n}\n\nconst JSON_EXT = '.json'\n\n/** namespace → 安全文件名(不含扩展名) */\nexport function toStorageFileName(namespace: string): string {\n const safe = namespace\n // eslint-disable-next-line no-control-regex -- 需要把控制字符也替换掉\n .replace(/[\\\\/:*?\"<>|\\u0000-\\u001f]/g, '_')\n .replace(/^\\.+/, '_')\n .trim()\n return (safe.length > 0 ? safe : 'default') + JSON_EXT\n}\n\nfunction joinPath(dir: string, name: string): string {\n const separator = dir.includes('\\\\') && !dir.includes('/') ? '\\\\' : '/'\n return dir.endsWith('/') || dir.endsWith('\\\\') ? `${dir}${name}` : `${dir}${separator}${name}`\n}\n\nexport function createFileStorage(dir: string, io: FileIoAdapter): StoragePort {\n const pathFor = (namespace: string) => joinPath(dir, toStorageFileName(namespace))\n\n return {\n async load(namespace) {\n const text = await io.readText(pathFor(namespace))\n if (text == null || text.trim().length === 0) return null\n return JSON.parse(text) as SerializedProject\n },\n\n async save(namespace, data) {\n const path = pathFor(namespace)\n try {\n await io.writeText(path, JSON.stringify(data, null, 2))\n return { ok: true, path }\n } catch (err) {\n return { ok: false, path, error: err instanceof Error ? err.message : String(err) }\n }\n },\n\n async remove(namespace) {\n await io.remove(pathFor(namespace))\n },\n\n async list() {\n const names = await io.list(dir)\n return names\n .filter((name) => name.toLowerCase().endsWith(JSON_EXT))\n .map((name) => name.slice(0, -JSON_EXT.length))\n .sort()\n },\n }\n}\n","/**\n * OPFS 可用性探测与临时文件辅助。\n *\n * WebAV 内部(opfs-tools)在模块加载期就会访问 `navigator.storage.getDirectory()`,\n * 因此这里只使用原生 File System Access API,不 import opfs-tools,避免重复副作用。\n *\n * 使用策略:小文件直接 `file.stream()` 喂给 MP4Clip;只有需要随机读取的大文件\n * 才落盘到 OPFS 临时目录(WebAV 自身也会在内部做同样的事,这里只提供探测与清理)。\n */\n\nconst TEMP_DIR = 'cut-test-tmp'\n\nexport interface OpfsProbeResult {\n available: boolean\n reason?: string\n}\n\nlet probeCache: OpfsProbeResult | null = null\n\nexport async function probeOpfs(force = false): Promise<OpfsProbeResult> {\n if (probeCache && !force) return probeCache\n try {\n if (typeof navigator === 'undefined' || navigator.storage?.getDirectory == null) {\n probeCache = { available: false, reason: '浏览器未实现 File System Access API。' }\n return probeCache\n }\n await navigator.storage.getDirectory()\n probeCache = { available: true }\n } catch (err) {\n probeCache = {\n available: false,\n reason: err instanceof Error ? err.message : String(err),\n }\n }\n return probeCache\n}\n\nasync function getTempDir(): Promise<FileSystemDirectoryHandle> {\n const root = await navigator.storage.getDirectory()\n return root.getDirectoryHandle(TEMP_DIR, { create: true })\n}\n\n/** 写入临时文件,返回文件名;调用方负责后续清理 */\nexport async function writeTempFile(name: string, data: Blob | ArrayBuffer): Promise<string> {\n const dir = await getTempDir()\n const handle = await dir.getFileHandle(name, { create: true })\n const writable = await handle.createWritable()\n await writable.write(data)\n await writable.close()\n return name\n}\n\n/** 读取临时文件 */\nexport async function readTempFile(name: string): Promise<File> {\n const dir = await getTempDir()\n const handle = await dir.getFileHandle(name)\n return handle.getFile()\n}\n\nexport async function removeTempFile(name: string): Promise<void> {\n try {\n const dir = await getTempDir()\n await dir.removeEntry(name)\n } catch {\n // 文件不存在时静默忽略:清理流程不应打断主流程\n }\n}\n\n/** 清空整个临时目录 */\nexport async function clearTempFiles(): Promise<void> {\n try {\n const root = await navigator.storage.getDirectory()\n await root.removeEntry(TEMP_DIR, { recursive: true })\n } catch {\n // 目录不存在时忽略\n }\n}\n\n/** 估算可用配额(用于导出前的提示) */\nexport async function estimateQuota(): Promise<{ usage: number; quota: number } | null> {\n try {\n const estimate = await navigator.storage.estimate()\n return { usage: estimate.usage ?? 0, quota: estimate.quota ?? 0 }\n } catch {\n return null\n }\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -24,10 +24,15 @@ export * from './timeline/canvas-size';
|
|
|
24
24
|
export * from './project/schema';
|
|
25
25
|
export type { StoragePort, SaveResult } from './project/storage';
|
|
26
26
|
export { createIndexedDbStorage, createMemoryStorage } from './project/indexeddb-storage';
|
|
27
|
+
export { createFileStorage, toStorageFileName } from './project/file-storage';
|
|
28
|
+
export type { FileIoAdapter } from './project/file-storage';
|
|
27
29
|
export type { HostBridge, HostPlatform, FilesPort, SaveFileResult, PickFilesOptions, PickFilesResult, } from './ports/host-bridge';
|
|
28
30
|
export { createWebBridge, downloadBlob } from './ports/web-bridge';
|
|
31
|
+
export * from './export/types';
|
|
32
|
+
export { ExportCancelledError, isExportCancelled } from './errors';
|
|
29
33
|
export * from './capability';
|
|
30
34
|
export * from './editor/index';
|
|
35
|
+
export type * from './editor/handle-types';
|
|
31
36
|
export * from './media/srt';
|
|
32
37
|
export * from './media/waveform';
|
|
33
38
|
export { UnsupportedFileError, detectFileType, fileToStream, bufferToStream, readTextFile, } from './media/file-reader';
|
package/dist/index.js
CHANGED
|
@@ -1,48 +1,51 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { i as
|
|
3
|
-
import {
|
|
1
|
+
import { _ as DEFAULT_STILL_DURATION_US, a as buildTicks, b as PROJECT_SCHEMA_VERSION, c as formatTimecode, d as secToUs, f as usToMs, g as DEFAULT_CLIP_VOLUME, h as createId, i as asUs, l as msToUs, m as usToSec, n as US_PER_SECOND, o as clamp, p as usToPx, r as asPx, s as formatDuration, t as US_PER_MS, u as pxToUs, v as HISTORY_MERGE_WINDOW_MS, y as MAX_HISTORY_STEPS } from "./chunks/time-C_gvkVl1.js";
|
|
2
|
+
import { A as createEmptyProject, C as rangesOverlap, D as trimClipData, E as splitClipData, F as getTrack, I as getTrackClips, L as getTrackEnd, M as getClipEnd, N as getProjectDuration, O as clampStillDuration, P as getSourceSpanUs, R as isTrackAudible, S as moveClipData, T as reorderTracksData, _ as sameTransform, a as remapClipsForSize, b as duplicateClipsData, c as RESIZE_HANDLES, d as handleCursor, f as handleLocalOffset, g as resizeTransform, h as rectCenter, i as ratioLabel, j as createTrack, k as createClip, l as degToRad, m as pointInTransform, n as isCustomSize, o as remapTransform, p as normalizeAngle, r as normalizeSize, s as MIN_TRANSFORM_SIZE, t as ASPECT_PRESETS, u as fromLocalPoint, v as toLocalPoint, w as removeClipsData, x as findOverlaps, y as compactTrackData } from "./chunks/canvas-size-CM2oKApi.js";
|
|
3
|
+
import { i as nearestSorted, n as computeSnap, r as computeSnapWithHysteresis, t as EMPTY_SNAP } from "./chunks/snap-CBV9G4xq.js";
|
|
4
|
+
import { A as createMemoryStorage, C as isExportCancelled, D as createWebBridge, E as resolveExportSettings, M as deserializeProject, N as serializeProject, O as downloadBlob, S as ExportCancelledError, T as createExportFilename, _ as detectDependencies, a as selectAssetById, b as isEnvironmentUsable, c as selectClips, d as selectProjectDuration, f as selectSelectedClipIds, g as ENGINE_DEPENDENCIES_INSTALL_COMMAND, h as createEventBus, i as createEditorStore, j as ProjectSchemaError, k as createIndexedDbStorage, l as selectPlayheadUs, m as createHistoryStore, n as computeWaveform, o as selectAssets, p as selectTracks, r as normalizeWaveform, s as selectClipById, t as buildWaveformPath, u as selectProject, v as detectEnvironment, w as DEFAULT_EXPORT_SETTINGS, x as summarizeCapabilities, y as detectOpfs } from "./chunks/waveform-DJkRtMct.js";
|
|
5
|
+
import { n as parseSrt, r as parseSrtDurationUs, t as isValidSrt } from "./chunks/srt-D539yJ-n.js";
|
|
4
6
|
import { a as readTextFile, i as fileToStream, n as bufferToStream, r as detectFileType, t as UnsupportedFileError } from "./chunks/file-reader-OjeI7HTg.js";
|
|
5
|
-
// src/
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// src/project/file-storage.ts
|
|
8
|
+
var JSON_EXT = ".json";
|
|
9
|
+
/** namespace → 安全文件名(不含扩展名) */
|
|
10
|
+
function toStorageFileName(namespace) {
|
|
11
|
+
const safe = namespace.replace(/[\\/:*?"<>|\u0000-\u001f]/g, "_").replace(/^\.+/, "_").trim();
|
|
12
|
+
return (safe.length > 0 ? safe : "default") + JSON_EXT;
|
|
9
13
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const cues = [];
|
|
14
|
-
for (const block of blocks) {
|
|
15
|
-
const lines = block.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
16
|
-
if (lines.length === 0) continue;
|
|
17
|
-
const timeLineIndex = lines.findIndex((l) => l.includes("-->"));
|
|
18
|
-
if (timeLineIndex < 0) continue;
|
|
19
|
-
const timeLine = lines[timeLineIndex] ?? "";
|
|
20
|
-
TIME_RE.lastIndex = 0;
|
|
21
|
-
const matches = Array.from(timeLine.matchAll(TIME_RE));
|
|
22
|
-
if (matches.length < 2) continue;
|
|
23
|
-
const [startMatch, endMatch] = matches;
|
|
24
|
-
if (startMatch == null || endMatch == null) continue;
|
|
25
|
-
const startMs = toMs(startMatch[1] ?? "0", startMatch[2] ?? "0", startMatch[3] ?? "0", startMatch[4] ?? "0");
|
|
26
|
-
const endMs = toMs(endMatch[1] ?? "0", endMatch[2] ?? "0", endMatch[3] ?? "0", endMatch[4] ?? "0");
|
|
27
|
-
if (endMs <= startMs) continue;
|
|
28
|
-
cues.push({
|
|
29
|
-
index: cues.length + 1,
|
|
30
|
-
startMs,
|
|
31
|
-
endMs,
|
|
32
|
-
text: lines.slice(timeLineIndex + 1).join("\n")
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
return cues;
|
|
36
|
-
}
|
|
37
|
-
/** 字幕总时长(微秒)。用于给字幕素材一个有限的 deration */
|
|
38
|
-
function parseSrtDurationUs(content) {
|
|
39
|
-
const cues = parseSrt(content);
|
|
40
|
-
const last = cues[cues.length - 1];
|
|
41
|
-
return last == null ? 0 : Math.round(last.endMs * 1e3);
|
|
14
|
+
function joinPath(dir, name) {
|
|
15
|
+
const separator = dir.includes("\\") && !dir.includes("/") ? "\\" : "/";
|
|
16
|
+
return dir.endsWith("/") || dir.endsWith("\\") ? `${dir}${name}` : `${dir}${separator}${name}`;
|
|
42
17
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return
|
|
18
|
+
function createFileStorage(dir, io) {
|
|
19
|
+
const pathFor = (namespace) => joinPath(dir, toStorageFileName(namespace));
|
|
20
|
+
return {
|
|
21
|
+
async load(namespace) {
|
|
22
|
+
const text = await io.readText(pathFor(namespace));
|
|
23
|
+
if (text == null || text.trim().length === 0) return null;
|
|
24
|
+
return JSON.parse(text);
|
|
25
|
+
},
|
|
26
|
+
async save(namespace, data) {
|
|
27
|
+
const path = pathFor(namespace);
|
|
28
|
+
try {
|
|
29
|
+
await io.writeText(path, JSON.stringify(data, null, 2));
|
|
30
|
+
return {
|
|
31
|
+
ok: true,
|
|
32
|
+
path
|
|
33
|
+
};
|
|
34
|
+
} catch (err) {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
path,
|
|
38
|
+
error: err instanceof Error ? err.message : String(err)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
async remove(namespace) {
|
|
43
|
+
await io.remove(pathFor(namespace));
|
|
44
|
+
},
|
|
45
|
+
async list() {
|
|
46
|
+
return (await io.list(dir)).filter((name) => name.toLowerCase().endsWith(JSON_EXT)).map((name) => name.slice(0, -5)).sort();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
46
49
|
}
|
|
47
50
|
// src/media/opfs.ts
|
|
48
51
|
/**
|
|
@@ -113,6 +116,6 @@ async function estimateQuota() {
|
|
|
113
116
|
return null;
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
|
-
export { ASPECT_PRESETS, DEFAULT_CLIP_VOLUME, DEFAULT_STILL_DURATION_US, EMPTY_SNAP, ENGINE_DEPENDENCIES_INSTALL_COMMAND, HISTORY_MERGE_WINDOW_MS, MAX_HISTORY_STEPS, MIN_TRANSFORM_SIZE, PROJECT_SCHEMA_VERSION, ProjectSchemaError, RESIZE_HANDLES, US_PER_MS, US_PER_SECOND, UnsupportedFileError, asPx, asUs, bufferToStream, buildTicks, buildWaveformPath, clamp, clampStillDuration, clearTempFiles, compactTrackData, computeSnap, computeSnapWithHysteresis, computeWaveform, createClip, createEditorStore, createEmptyProject, createEventBus, createHistoryStore, createId, createIndexedDbStorage, createMemoryStorage, createTrack, createWebBridge, degToRad, deserializeProject, detectDependencies, detectEnvironment, detectFileType, detectOpfs, downloadBlob, duplicateClipsData, estimateQuota, fileToStream, findOverlaps, formatDuration, formatTimecode, fromLocalPoint, getClipEnd, getProjectDuration, getSourceSpanUs, getTrack, getTrackClips, getTrackEnd, handleCursor, handleLocalOffset, isCustomSize, isEnvironmentUsable, isTrackAudible, isValidSrt, moveClipData, msToUs, nearestSorted, normalizeAngle, normalizeSize, normalizeWaveform, parseSrt, parseSrtDurationUs, pointInTransform, probeOpfs, pxToUs, rangesOverlap, ratioLabel, readTempFile, readTextFile, rectCenter, remapClipsForSize, remapTransform, removeClipsData, removeTempFile, reorderTracksData, resizeTransform, sameTransform, secToUs, selectAssetById, selectAssets, selectClipById, selectClips, selectPlayheadUs, selectProject, selectProjectDuration, selectSelectedClipIds, selectTracks, serializeProject, splitClipData, summarizeCapabilities, toLocalPoint, trimClipData, usToMs, usToPx, usToSec, writeTempFile };
|
|
119
|
+
export { ASPECT_PRESETS, DEFAULT_CLIP_VOLUME, DEFAULT_EXPORT_SETTINGS, DEFAULT_STILL_DURATION_US, EMPTY_SNAP, ENGINE_DEPENDENCIES_INSTALL_COMMAND, ExportCancelledError, HISTORY_MERGE_WINDOW_MS, MAX_HISTORY_STEPS, MIN_TRANSFORM_SIZE, PROJECT_SCHEMA_VERSION, ProjectSchemaError, RESIZE_HANDLES, US_PER_MS, US_PER_SECOND, UnsupportedFileError, asPx, asUs, bufferToStream, buildTicks, buildWaveformPath, clamp, clampStillDuration, clearTempFiles, compactTrackData, computeSnap, computeSnapWithHysteresis, computeWaveform, createClip, createEditorStore, createEmptyProject, createEventBus, createExportFilename, createFileStorage, createHistoryStore, createId, createIndexedDbStorage, createMemoryStorage, createTrack, createWebBridge, degToRad, deserializeProject, detectDependencies, detectEnvironment, detectFileType, detectOpfs, downloadBlob, duplicateClipsData, estimateQuota, fileToStream, findOverlaps, formatDuration, formatTimecode, fromLocalPoint, getClipEnd, getProjectDuration, getSourceSpanUs, getTrack, getTrackClips, getTrackEnd, handleCursor, handleLocalOffset, isCustomSize, isEnvironmentUsable, isExportCancelled, isTrackAudible, isValidSrt, moveClipData, msToUs, nearestSorted, normalizeAngle, normalizeSize, normalizeWaveform, parseSrt, parseSrtDurationUs, pointInTransform, probeOpfs, pxToUs, rangesOverlap, ratioLabel, readTempFile, readTextFile, rectCenter, remapClipsForSize, remapTransform, removeClipsData, removeTempFile, reorderTracksData, resizeTransform, resolveExportSettings, sameTransform, secToUs, selectAssetById, selectAssets, selectClipById, selectClips, selectPlayheadUs, selectProject, selectProjectDuration, selectSelectedClipIds, selectTracks, serializeProject, splitClipData, summarizeCapabilities, toLocalPoint, toStorageFileName, trimClipData, usToMs, usToPx, usToSec, writeTempFile };
|
|
117
120
|
|
|
118
121
|
//# sourceMappingURL=index.js.map
|