@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.
- package/dist/chunks/{canvas-size-IzoEg2cq.cjs → canvas-size-DIABzEW2.cjs} +209 -302
- package/dist/chunks/canvas-size-DIABzEW2.cjs.map +1 -0
- package/dist/chunks/{canvas-size-iWL8zZPl.js → canvas-size-Dlnng74w.js} +168 -171
- package/dist/chunks/canvas-size-Dlnng74w.js.map +1 -0
- package/dist/chunks/id-CJsdlGei.js +35 -0
- package/dist/chunks/id-CJsdlGei.js.map +1 -0
- package/dist/chunks/id-DaCxD4-5.cjs +70 -0
- package/dist/chunks/id-DaCxD4-5.cjs.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-CeYuTN6d.cjs → snap-Cfk15JVg.cjs} +3 -23
- package/dist/chunks/{snap-CeYuTN6d.cjs.map → snap-Cfk15JVg.cjs.map} +1 -1
- package/dist/chunks/{snap-Dc022smD.js → snap-DpkVPVJN.js} +3 -17
- package/dist/chunks/{snap-Dc022smD.js.map → snap-DpkVPVJN.js.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/thumbnails-DEjnao5r.cjs +81 -0
- package/dist/chunks/thumbnails-DEjnao5r.cjs.map +1 -0
- package/dist/chunks/thumbnails-qH0dX_Zs.js +64 -0
- package/dist/chunks/thumbnails-qH0dX_Zs.js.map +1 -0
- package/dist/chunks/time-CYEd91qN.cjs +196 -0
- package/dist/chunks/time-CYEd91qN.cjs.map +1 -0
- package/dist/chunks/time-ChF1GwCy.js +113 -0
- package/dist/chunks/time-ChF1GwCy.js.map +1 -0
- package/dist/chunks/{waveform-Bhvv2t7-.cjs → waveform-BYwDdKpC.cjs} +150 -31
- package/dist/chunks/waveform-BYwDdKpC.cjs.map +1 -0
- package/dist/chunks/{waveform-CWU8Fa-n.js → waveform-uyGNE01H.js} +111 -22
- package/dist/chunks/waveform-uyGNE01H.js.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 +1185 -455
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.ts +3 -0
- package/dist/engine/index.js +1169 -446
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/webav/export-runner.d.ts +61 -0
- package/dist/engine/webav/exporter.d.ts +3 -1
- package/dist/engine/webav/import-files.d.ts +58 -0
- package/dist/engine/webav/import-pipeline.d.ts +23 -1
- package/dist/engine/webav/types.d.ts +15 -0
- package/dist/errors.d.ts +12 -0
- package/dist/export/types.d.ts +49 -0
- package/dist/index.cjs +83 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +47 -43
- package/dist/index.js.map +1 -1
- package/dist/lib/error.d.ts +6 -0
- package/dist/media/thumbnails.d.ts +8 -0
- package/dist/ports/host-bridge.d.ts +7 -0
- package/dist/project/file-storage.d.ts +23 -0
- package/dist/project/schema.d.ts +16 -4
- package/dist/timeline/index.cjs +24 -19
- package/dist/timeline/index.d.ts +1 -0
- package/dist/timeline/index.js +4 -3
- package/dist/timeline/normalize.d.ts +28 -0
- package/dist/timeline/ops.d.ts +0 -4
- package/dist/timeline/snap.d.ts +0 -2
- package/dist/types.d.ts +14 -2
- package/package.json +3 -2
- package/dist/chunks/canvas-size-IzoEg2cq.cjs.map +0 -1
- package/dist/chunks/canvas-size-iWL8zZPl.js.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,196 @@
|
|
|
1
|
+
// src/timeline/time.ts
|
|
2
|
+
var US_PER_SECOND = 1e6;
|
|
3
|
+
var US_PER_MS = 1e3;
|
|
4
|
+
/** 秒 → 微秒 */
|
|
5
|
+
function secToUs(sec) {
|
|
6
|
+
return Math.round(sec * US_PER_SECOND);
|
|
7
|
+
}
|
|
8
|
+
/** 微秒 → 秒 */
|
|
9
|
+
function usToSec(us) {
|
|
10
|
+
return us / US_PER_SECOND;
|
|
11
|
+
}
|
|
12
|
+
/** 毫秒 → 微秒 */
|
|
13
|
+
function msToUs(ms) {
|
|
14
|
+
return Math.round(ms * US_PER_MS);
|
|
15
|
+
}
|
|
16
|
+
/** 微秒 → 毫秒 */
|
|
17
|
+
function usToMs(us) {
|
|
18
|
+
return Math.round(us / US_PER_MS);
|
|
19
|
+
}
|
|
20
|
+
/** 数字 → 微秒(用于从 DOM/WebAV 回调拿到的裸数字) */
|
|
21
|
+
function asUs(n) {
|
|
22
|
+
return Math.round(n);
|
|
23
|
+
}
|
|
24
|
+
/** 数字 → 像素 */
|
|
25
|
+
function asPx(n) {
|
|
26
|
+
return n;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 微秒 → 像素。
|
|
30
|
+
* @param us 时间(微秒)
|
|
31
|
+
* @param pxPerSecond 每秒对应多少像素
|
|
32
|
+
*/
|
|
33
|
+
function usToPx(us, pxPerSecond) {
|
|
34
|
+
return us / US_PER_SECOND * pxPerSecond;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 像素 → 微秒。
|
|
38
|
+
* @param px 像素
|
|
39
|
+
* @param pxPerSecond 每秒对应多少像素
|
|
40
|
+
*/
|
|
41
|
+
function pxToUs(px, pxPerSecond) {
|
|
42
|
+
return Math.round(px / pxPerSecond * US_PER_SECOND);
|
|
43
|
+
}
|
|
44
|
+
/** 微秒 → `HH:MM:SS.mmm` */
|
|
45
|
+
function formatTimecode(us, showMs = true) {
|
|
46
|
+
const totalMs = Math.floor(Math.max(0, us) / 1e3);
|
|
47
|
+
const ms = totalMs % 1e3;
|
|
48
|
+
const totalSec = Math.floor(totalMs / 1e3);
|
|
49
|
+
const sec = totalSec % 60;
|
|
50
|
+
const min = Math.floor(totalSec / 60) % 60;
|
|
51
|
+
const hour = Math.floor(totalSec / 3600);
|
|
52
|
+
const pad = (n, len = 2) => String(n).padStart(len, "0");
|
|
53
|
+
const base = `${pad(hour)}:${pad(min)}:${pad(sec)}`;
|
|
54
|
+
return showMs ? `${base}.${pad(ms, 3)}` : base;
|
|
55
|
+
}
|
|
56
|
+
/** 微秒 → `MM:SS` 或 `HH:MM:SS`,列表展示用 */
|
|
57
|
+
function formatDuration(us) {
|
|
58
|
+
if (!Number.isFinite(us)) return "∞";
|
|
59
|
+
const totalSec = Math.max(0, Math.round(us / US_PER_SECOND));
|
|
60
|
+
const sec = totalSec % 60;
|
|
61
|
+
const min = Math.floor(totalSec / 60) % 60;
|
|
62
|
+
const hour = Math.floor(totalSec / 3600);
|
|
63
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
64
|
+
return hour > 0 ? `${hour}:${pad(min)}:${pad(sec)}` : `${pad(min)}:${pad(sec)}`;
|
|
65
|
+
}
|
|
66
|
+
var TICK_STEPS_US = [
|
|
67
|
+
1e5,
|
|
68
|
+
2e5,
|
|
69
|
+
5e5,
|
|
70
|
+
1e6,
|
|
71
|
+
2e6,
|
|
72
|
+
5e6,
|
|
73
|
+
1e7,
|
|
74
|
+
15e6,
|
|
75
|
+
3e7,
|
|
76
|
+
6e7,
|
|
77
|
+
12e7,
|
|
78
|
+
3e8,
|
|
79
|
+
6e8
|
|
80
|
+
];
|
|
81
|
+
/**
|
|
82
|
+
* 根据缩放级别生成刻度尺。
|
|
83
|
+
*
|
|
84
|
+
* @param pxPerSecond 每秒像素
|
|
85
|
+
* @param viewportWidthPx 可视宽度(像素)
|
|
86
|
+
* @param scrollLeftPx 横向滚动偏移(像素)
|
|
87
|
+
* @returns 主刻度(带文字)与次刻度
|
|
88
|
+
*/
|
|
89
|
+
function buildTicks(pxPerSecond, viewportWidthPx, scrollLeftPx = 0) {
|
|
90
|
+
const startUs = pxToUs(scrollLeftPx, pxPerSecond);
|
|
91
|
+
const endUs = pxToUs(scrollLeftPx + viewportWidthPx, pxPerSecond);
|
|
92
|
+
const minLabelSpacingPx = 72;
|
|
93
|
+
const step = TICK_STEPS_US.find((s) => usToPx(s, pxPerSecond) >= minLabelSpacingPx) ?? TICK_STEPS_US[TICK_STEPS_US.length - 1];
|
|
94
|
+
const minorStep = step / 5;
|
|
95
|
+
const first = Math.floor(startUs / step) * step;
|
|
96
|
+
const ticks = [];
|
|
97
|
+
for (let us = first; us <= endUs + step; us += minorStep) {
|
|
98
|
+
if (us < 0) continue;
|
|
99
|
+
ticks.push({
|
|
100
|
+
us,
|
|
101
|
+
px: usToPx(us, pxPerSecond),
|
|
102
|
+
major: us % step === 0
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return ticks;
|
|
106
|
+
}
|
|
107
|
+
/** 保证值落在 [min, max] */
|
|
108
|
+
function clamp(value, min, max) {
|
|
109
|
+
return Math.min(max, Math.max(min, value));
|
|
110
|
+
}
|
|
111
|
+
Object.defineProperty(exports, "US_PER_MS", {
|
|
112
|
+
enumerable: true,
|
|
113
|
+
get: function() {
|
|
114
|
+
return US_PER_MS;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
Object.defineProperty(exports, "US_PER_SECOND", {
|
|
118
|
+
enumerable: true,
|
|
119
|
+
get: function() {
|
|
120
|
+
return US_PER_SECOND;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
Object.defineProperty(exports, "asPx", {
|
|
124
|
+
enumerable: true,
|
|
125
|
+
get: function() {
|
|
126
|
+
return asPx;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
Object.defineProperty(exports, "asUs", {
|
|
130
|
+
enumerable: true,
|
|
131
|
+
get: function() {
|
|
132
|
+
return asUs;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
Object.defineProperty(exports, "buildTicks", {
|
|
136
|
+
enumerable: true,
|
|
137
|
+
get: function() {
|
|
138
|
+
return buildTicks;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
Object.defineProperty(exports, "clamp", {
|
|
142
|
+
enumerable: true,
|
|
143
|
+
get: function() {
|
|
144
|
+
return clamp;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
Object.defineProperty(exports, "formatDuration", {
|
|
148
|
+
enumerable: true,
|
|
149
|
+
get: function() {
|
|
150
|
+
return formatDuration;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
Object.defineProperty(exports, "formatTimecode", {
|
|
154
|
+
enumerable: true,
|
|
155
|
+
get: function() {
|
|
156
|
+
return formatTimecode;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
Object.defineProperty(exports, "msToUs", {
|
|
160
|
+
enumerable: true,
|
|
161
|
+
get: function() {
|
|
162
|
+
return msToUs;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
Object.defineProperty(exports, "pxToUs", {
|
|
166
|
+
enumerable: true,
|
|
167
|
+
get: function() {
|
|
168
|
+
return pxToUs;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
Object.defineProperty(exports, "secToUs", {
|
|
172
|
+
enumerable: true,
|
|
173
|
+
get: function() {
|
|
174
|
+
return secToUs;
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
Object.defineProperty(exports, "usToMs", {
|
|
178
|
+
enumerable: true,
|
|
179
|
+
get: function() {
|
|
180
|
+
return usToMs;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
Object.defineProperty(exports, "usToPx", {
|
|
184
|
+
enumerable: true,
|
|
185
|
+
get: function() {
|
|
186
|
+
return usToPx;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
Object.defineProperty(exports, "usToSec", {
|
|
190
|
+
enumerable: true,
|
|
191
|
+
get: function() {
|
|
192
|
+
return usToSec;
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
//# sourceMappingURL=time-CYEd91qN.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-CYEd91qN.cjs","names":[],"sources":["../../src/timeline/time.ts"],"sourcesContent":["/**\n * 时间与像素的单位换算。\n *\n * 内部统一使用微秒(µs)。为避免中途混用毫秒/秒,这里定义 branded type:\n * - `Us` 微秒\n * - `Ms` 毫秒\n * - `Px` 像素\n *\n * 只在函数边界处做 cast,业务代码不允许出现裸 number 参与时间计算。\n */\n\ndeclare const usBrand: unique symbol\ndeclare const msBrand: unique symbol\ndeclare const pxBrand: unique symbol\n\nexport type Us = number & { readonly [usBrand]: true }\nexport type Ms = number & { readonly [msBrand]: true }\nexport type Px = number & { readonly [pxBrand]: true }\n\nexport const US_PER_SECOND = 1_000_000\nexport const US_PER_MS = 1_000\n\n/** 秒 → 微秒 */\nexport function secToUs(sec: number): Us {\n return Math.round(sec * US_PER_SECOND) as Us\n}\n\n/** 微秒 → 秒 */\nexport function usToSec(us: number): number {\n return us / US_PER_SECOND\n}\n\n/** 毫秒 → 微秒 */\nexport function msToUs(ms: number): Us {\n return Math.round(ms * US_PER_MS) as Us\n}\n\n/** 微秒 → 毫秒 */\nexport function usToMs(us: number): Ms {\n return Math.round(us / US_PER_MS) as Ms\n}\n\n/** 数字 → 微秒(用于从 DOM/WebAV 回调拿到的裸数字) */\nexport function asUs(n: number): Us {\n return Math.round(n) as Us\n}\n\n/** 数字 → 像素 */\nexport function asPx(n: number): Px {\n return n as Px\n}\n\n/**\n * 微秒 → 像素。\n * @param us 时间(微秒)\n * @param pxPerSecond 每秒对应多少像素\n */\nexport function usToPx(us: number, pxPerSecond: number): Px {\n return ((us / US_PER_SECOND) * pxPerSecond) as Px\n}\n\n/**\n * 像素 → 微秒。\n * @param px 像素\n * @param pxPerSecond 每秒对应多少像素\n */\nexport function pxToUs(px: number, pxPerSecond: number): Us {\n return Math.round((px / pxPerSecond) * US_PER_SECOND) as Us\n}\n\n/** 微秒 → `HH:MM:SS.mmm` */\nexport function formatTimecode(us: number, showMs = true): string {\n const safe = Math.max(0, us)\n const totalMs = Math.floor(safe / 1000)\n const ms = totalMs % 1000\n const totalSec = Math.floor(totalMs / 1000)\n const sec = totalSec % 60\n const min = Math.floor(totalSec / 60) % 60\n const hour = Math.floor(totalSec / 3600)\n const pad = (n: number, len = 2) => String(n).padStart(len, '0')\n const base = `${pad(hour)}:${pad(min)}:${pad(sec)}`\n return showMs ? `${base}.${pad(ms, 3)}` : base\n}\n\n/** 微秒 → `MM:SS` 或 `HH:MM:SS`,列表展示用 */\nexport function formatDuration(us: number): string {\n if (!Number.isFinite(us)) return '∞'\n const totalSec = Math.max(0, Math.round(us / US_PER_SECOND))\n const sec = totalSec % 60\n const min = Math.floor(totalSec / 60) % 60\n const hour = Math.floor(totalSec / 3600)\n const pad = (n: number) => String(n).padStart(2, '0')\n return hour > 0 ? `${hour}:${pad(min)}:${pad(sec)}` : `${pad(min)}:${pad(sec)}`\n}\n\nexport interface RulerTick {\n us: Us\n px: Px\n major: boolean\n}\n\nconst TICK_STEPS_US = [\n 100_000, 200_000, 500_000, 1_000_000, 2_000_000, 5_000_000, 10_000_000, 15_000_000,\n 30_000_000, 60_000_000, 120_000_000, 300_000_000, 600_000_000,\n]\n\n/**\n * 根据缩放级别生成刻度尺。\n *\n * @param pxPerSecond 每秒像素\n * @param viewportWidthPx 可视宽度(像素)\n * @param scrollLeftPx 横向滚动偏移(像素)\n * @returns 主刻度(带文字)与次刻度\n */\nexport function buildTicks(\n pxPerSecond: number,\n viewportWidthPx: number,\n scrollLeftPx = 0,\n): RulerTick[] {\n const startUs = pxToUs(scrollLeftPx, pxPerSecond)\n const endUs = pxToUs(scrollLeftPx + viewportWidthPx, pxPerSecond)\n const minLabelSpacingPx = 72\n const step =\n TICK_STEPS_US.find((s) => usToPx(s, pxPerSecond) >= minLabelSpacingPx) ??\n TICK_STEPS_US[TICK_STEPS_US.length - 1]\n const minorStep = step / 5\n const first = Math.floor(startUs / step) * step\n const ticks: RulerTick[] = []\n for (let us = first; us <= endUs + step; us += minorStep) {\n if (us < 0) continue\n ticks.push({\n us: us as Us,\n px: usToPx(us, pxPerSecond),\n major: us % step === 0,\n })\n }\n return ticks\n}\n\n/** 保证值落在 [min, max] */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n"],"mappings":";AAmBA,IAAa,gBAAgB;AAC7B,IAAa,YAAY;;AAGzB,SAAgB,QAAQ,KAAiB;CACvC,OAAO,KAAK,MAAM,MAAM,aAAa;AACvC;;AAGA,SAAgB,QAAQ,IAAoB;CAC1C,OAAO,KAAK;AACd;;AAGA,SAAgB,OAAO,IAAgB;CACrC,OAAO,KAAK,MAAM,KAAK,SAAS;AAClC;;AAGA,SAAgB,OAAO,IAAgB;CACrC,OAAO,KAAK,MAAM,KAAK,SAAS;AAClC;;AAGA,SAAgB,KAAK,GAAe;CAClC,OAAO,KAAK,MAAM,CAAC;AACrB;;AAGA,SAAgB,KAAK,GAAe;CAClC,OAAO;AACT;;;;;;AAOA,SAAgB,OAAO,IAAY,aAAyB;CAC1D,OAAS,KAAK,gBAAiB;AACjC;;;;;;AAOA,SAAgB,OAAO,IAAY,aAAyB;CAC1D,OAAO,KAAK,MAAO,KAAK,cAAe,aAAa;AACtD;;AAGA,SAAgB,eAAe,IAAY,SAAS,MAAc;CAEhE,MAAM,UAAU,KAAK,MADR,KAAK,IAAI,GAAG,EACE,IAAO,GAAI;CACtC,MAAM,KAAK,UAAU;CACrB,MAAM,WAAW,KAAK,MAAM,UAAU,GAAI;CAC1C,MAAM,MAAM,WAAW;CACvB,MAAM,MAAM,KAAK,MAAM,WAAW,EAAE,IAAI;CACxC,MAAM,OAAO,KAAK,MAAM,WAAW,IAAI;CACvC,MAAM,OAAO,GAAW,MAAM,MAAM,OAAO,CAAC,CAAC,CAAC,SAAS,KAAK,GAAG;CAC/D,MAAM,OAAO,GAAG,IAAI,IAAI,EAAE,GAAG,IAAI,GAAG,EAAE,GAAG,IAAI,GAAG;CAChD,OAAO,SAAS,GAAG,KAAK,GAAG,IAAI,IAAI,CAAC,MAAM;AAC5C;;AAGA,SAAgB,eAAe,IAAoB;CACjD,IAAI,CAAC,OAAO,SAAS,EAAE,GAAG,OAAO;CACjC,MAAM,WAAW,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,aAAa,CAAC;CAC3D,MAAM,MAAM,WAAW;CACvB,MAAM,MAAM,KAAK,MAAM,WAAW,EAAE,IAAI;CACxC,MAAM,OAAO,KAAK,MAAM,WAAW,IAAI;CACvC,MAAM,OAAO,MAAc,OAAO,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;CACpD,OAAO,OAAO,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,EAAE,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,EAAE,GAAG,IAAI,GAAG;AAC9E;AAQA,IAAM,gBAAgB;CACpB;CAAS;CAAS;CAAS;CAAW;CAAW;CAAW;CAAY;CACxE;CAAY;CAAY;CAAa;CAAa;AACpD;;;;;;;;;AAUA,SAAgB,WACd,aACA,iBACA,eAAe,GACF;CACb,MAAM,UAAU,OAAO,cAAc,WAAW;CAChD,MAAM,QAAQ,OAAO,eAAe,iBAAiB,WAAW;CAChE,MAAM,oBAAoB;CAC1B,MAAM,OACJ,cAAc,MAAM,MAAM,OAAO,GAAG,WAAW,KAAK,iBAAiB,KACrE,cAAc,cAAc,SAAS;CACvC,MAAM,YAAY,OAAO;CACzB,MAAM,QAAQ,KAAK,MAAM,UAAU,IAAI,IAAI;CAC3C,MAAM,QAAqB,CAAC;CAC5B,KAAK,IAAI,KAAK,OAAO,MAAM,QAAQ,MAAM,MAAM,WAAW;EACxD,IAAI,KAAK,GAAG;EACZ,MAAM,KAAK;GACL;GACJ,IAAI,OAAO,IAAI,WAAW;GAC1B,OAAO,KAAK,SAAS;EACvB,CAAC;CACH;CACA,OAAO;AACT;;AAGA,SAAgB,MAAM,OAAe,KAAa,KAAqB;CACrE,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC3C"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// src/timeline/time.ts
|
|
2
|
+
var US_PER_SECOND = 1e6;
|
|
3
|
+
var US_PER_MS = 1e3;
|
|
4
|
+
/** 秒 → 微秒 */
|
|
5
|
+
function secToUs(sec) {
|
|
6
|
+
return Math.round(sec * US_PER_SECOND);
|
|
7
|
+
}
|
|
8
|
+
/** 微秒 → 秒 */
|
|
9
|
+
function usToSec(us) {
|
|
10
|
+
return us / US_PER_SECOND;
|
|
11
|
+
}
|
|
12
|
+
/** 毫秒 → 微秒 */
|
|
13
|
+
function msToUs(ms) {
|
|
14
|
+
return Math.round(ms * US_PER_MS);
|
|
15
|
+
}
|
|
16
|
+
/** 微秒 → 毫秒 */
|
|
17
|
+
function usToMs(us) {
|
|
18
|
+
return Math.round(us / US_PER_MS);
|
|
19
|
+
}
|
|
20
|
+
/** 数字 → 微秒(用于从 DOM/WebAV 回调拿到的裸数字) */
|
|
21
|
+
function asUs(n) {
|
|
22
|
+
return Math.round(n);
|
|
23
|
+
}
|
|
24
|
+
/** 数字 → 像素 */
|
|
25
|
+
function asPx(n) {
|
|
26
|
+
return n;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 微秒 → 像素。
|
|
30
|
+
* @param us 时间(微秒)
|
|
31
|
+
* @param pxPerSecond 每秒对应多少像素
|
|
32
|
+
*/
|
|
33
|
+
function usToPx(us, pxPerSecond) {
|
|
34
|
+
return us / US_PER_SECOND * pxPerSecond;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 像素 → 微秒。
|
|
38
|
+
* @param px 像素
|
|
39
|
+
* @param pxPerSecond 每秒对应多少像素
|
|
40
|
+
*/
|
|
41
|
+
function pxToUs(px, pxPerSecond) {
|
|
42
|
+
return Math.round(px / pxPerSecond * US_PER_SECOND);
|
|
43
|
+
}
|
|
44
|
+
/** 微秒 → `HH:MM:SS.mmm` */
|
|
45
|
+
function formatTimecode(us, showMs = true) {
|
|
46
|
+
const totalMs = Math.floor(Math.max(0, us) / 1e3);
|
|
47
|
+
const ms = totalMs % 1e3;
|
|
48
|
+
const totalSec = Math.floor(totalMs / 1e3);
|
|
49
|
+
const sec = totalSec % 60;
|
|
50
|
+
const min = Math.floor(totalSec / 60) % 60;
|
|
51
|
+
const hour = Math.floor(totalSec / 3600);
|
|
52
|
+
const pad = (n, len = 2) => String(n).padStart(len, "0");
|
|
53
|
+
const base = `${pad(hour)}:${pad(min)}:${pad(sec)}`;
|
|
54
|
+
return showMs ? `${base}.${pad(ms, 3)}` : base;
|
|
55
|
+
}
|
|
56
|
+
/** 微秒 → `MM:SS` 或 `HH:MM:SS`,列表展示用 */
|
|
57
|
+
function formatDuration(us) {
|
|
58
|
+
if (!Number.isFinite(us)) return "∞";
|
|
59
|
+
const totalSec = Math.max(0, Math.round(us / US_PER_SECOND));
|
|
60
|
+
const sec = totalSec % 60;
|
|
61
|
+
const min = Math.floor(totalSec / 60) % 60;
|
|
62
|
+
const hour = Math.floor(totalSec / 3600);
|
|
63
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
64
|
+
return hour > 0 ? `${hour}:${pad(min)}:${pad(sec)}` : `${pad(min)}:${pad(sec)}`;
|
|
65
|
+
}
|
|
66
|
+
var TICK_STEPS_US = [
|
|
67
|
+
1e5,
|
|
68
|
+
2e5,
|
|
69
|
+
5e5,
|
|
70
|
+
1e6,
|
|
71
|
+
2e6,
|
|
72
|
+
5e6,
|
|
73
|
+
1e7,
|
|
74
|
+
15e6,
|
|
75
|
+
3e7,
|
|
76
|
+
6e7,
|
|
77
|
+
12e7,
|
|
78
|
+
3e8,
|
|
79
|
+
6e8
|
|
80
|
+
];
|
|
81
|
+
/**
|
|
82
|
+
* 根据缩放级别生成刻度尺。
|
|
83
|
+
*
|
|
84
|
+
* @param pxPerSecond 每秒像素
|
|
85
|
+
* @param viewportWidthPx 可视宽度(像素)
|
|
86
|
+
* @param scrollLeftPx 横向滚动偏移(像素)
|
|
87
|
+
* @returns 主刻度(带文字)与次刻度
|
|
88
|
+
*/
|
|
89
|
+
function buildTicks(pxPerSecond, viewportWidthPx, scrollLeftPx = 0) {
|
|
90
|
+
const startUs = pxToUs(scrollLeftPx, pxPerSecond);
|
|
91
|
+
const endUs = pxToUs(scrollLeftPx + viewportWidthPx, pxPerSecond);
|
|
92
|
+
const minLabelSpacingPx = 72;
|
|
93
|
+
const step = TICK_STEPS_US.find((s) => usToPx(s, pxPerSecond) >= minLabelSpacingPx) ?? TICK_STEPS_US[TICK_STEPS_US.length - 1];
|
|
94
|
+
const minorStep = step / 5;
|
|
95
|
+
const first = Math.floor(startUs / step) * step;
|
|
96
|
+
const ticks = [];
|
|
97
|
+
for (let us = first; us <= endUs + step; us += minorStep) {
|
|
98
|
+
if (us < 0) continue;
|
|
99
|
+
ticks.push({
|
|
100
|
+
us,
|
|
101
|
+
px: usToPx(us, pxPerSecond),
|
|
102
|
+
major: us % step === 0
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return ticks;
|
|
106
|
+
}
|
|
107
|
+
/** 保证值落在 [min, max] */
|
|
108
|
+
function clamp(value, min, max) {
|
|
109
|
+
return Math.min(max, Math.max(min, value));
|
|
110
|
+
}
|
|
111
|
+
export { buildTicks as a, formatTimecode as c, secToUs as d, usToMs as f, asUs as i, msToUs as l, usToSec as m, US_PER_SECOND as n, clamp as o, usToPx as p, asPx as r, formatDuration as s, US_PER_MS as t, pxToUs as u };
|
|
112
|
+
|
|
113
|
+
//# sourceMappingURL=time-ChF1GwCy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-ChF1GwCy.js","names":[],"sources":["../../src/timeline/time.ts"],"sourcesContent":["/**\n * 时间与像素的单位换算。\n *\n * 内部统一使用微秒(µs)。为避免中途混用毫秒/秒,这里定义 branded type:\n * - `Us` 微秒\n * - `Ms` 毫秒\n * - `Px` 像素\n *\n * 只在函数边界处做 cast,业务代码不允许出现裸 number 参与时间计算。\n */\n\ndeclare const usBrand: unique symbol\ndeclare const msBrand: unique symbol\ndeclare const pxBrand: unique symbol\n\nexport type Us = number & { readonly [usBrand]: true }\nexport type Ms = number & { readonly [msBrand]: true }\nexport type Px = number & { readonly [pxBrand]: true }\n\nexport const US_PER_SECOND = 1_000_000\nexport const US_PER_MS = 1_000\n\n/** 秒 → 微秒 */\nexport function secToUs(sec: number): Us {\n return Math.round(sec * US_PER_SECOND) as Us\n}\n\n/** 微秒 → 秒 */\nexport function usToSec(us: number): number {\n return us / US_PER_SECOND\n}\n\n/** 毫秒 → 微秒 */\nexport function msToUs(ms: number): Us {\n return Math.round(ms * US_PER_MS) as Us\n}\n\n/** 微秒 → 毫秒 */\nexport function usToMs(us: number): Ms {\n return Math.round(us / US_PER_MS) as Ms\n}\n\n/** 数字 → 微秒(用于从 DOM/WebAV 回调拿到的裸数字) */\nexport function asUs(n: number): Us {\n return Math.round(n) as Us\n}\n\n/** 数字 → 像素 */\nexport function asPx(n: number): Px {\n return n as Px\n}\n\n/**\n * 微秒 → 像素。\n * @param us 时间(微秒)\n * @param pxPerSecond 每秒对应多少像素\n */\nexport function usToPx(us: number, pxPerSecond: number): Px {\n return ((us / US_PER_SECOND) * pxPerSecond) as Px\n}\n\n/**\n * 像素 → 微秒。\n * @param px 像素\n * @param pxPerSecond 每秒对应多少像素\n */\nexport function pxToUs(px: number, pxPerSecond: number): Us {\n return Math.round((px / pxPerSecond) * US_PER_SECOND) as Us\n}\n\n/** 微秒 → `HH:MM:SS.mmm` */\nexport function formatTimecode(us: number, showMs = true): string {\n const safe = Math.max(0, us)\n const totalMs = Math.floor(safe / 1000)\n const ms = totalMs % 1000\n const totalSec = Math.floor(totalMs / 1000)\n const sec = totalSec % 60\n const min = Math.floor(totalSec / 60) % 60\n const hour = Math.floor(totalSec / 3600)\n const pad = (n: number, len = 2) => String(n).padStart(len, '0')\n const base = `${pad(hour)}:${pad(min)}:${pad(sec)}`\n return showMs ? `${base}.${pad(ms, 3)}` : base\n}\n\n/** 微秒 → `MM:SS` 或 `HH:MM:SS`,列表展示用 */\nexport function formatDuration(us: number): string {\n if (!Number.isFinite(us)) return '∞'\n const totalSec = Math.max(0, Math.round(us / US_PER_SECOND))\n const sec = totalSec % 60\n const min = Math.floor(totalSec / 60) % 60\n const hour = Math.floor(totalSec / 3600)\n const pad = (n: number) => String(n).padStart(2, '0')\n return hour > 0 ? `${hour}:${pad(min)}:${pad(sec)}` : `${pad(min)}:${pad(sec)}`\n}\n\nexport interface RulerTick {\n us: Us\n px: Px\n major: boolean\n}\n\nconst TICK_STEPS_US = [\n 100_000, 200_000, 500_000, 1_000_000, 2_000_000, 5_000_000, 10_000_000, 15_000_000,\n 30_000_000, 60_000_000, 120_000_000, 300_000_000, 600_000_000,\n]\n\n/**\n * 根据缩放级别生成刻度尺。\n *\n * @param pxPerSecond 每秒像素\n * @param viewportWidthPx 可视宽度(像素)\n * @param scrollLeftPx 横向滚动偏移(像素)\n * @returns 主刻度(带文字)与次刻度\n */\nexport function buildTicks(\n pxPerSecond: number,\n viewportWidthPx: number,\n scrollLeftPx = 0,\n): RulerTick[] {\n const startUs = pxToUs(scrollLeftPx, pxPerSecond)\n const endUs = pxToUs(scrollLeftPx + viewportWidthPx, pxPerSecond)\n const minLabelSpacingPx = 72\n const step =\n TICK_STEPS_US.find((s) => usToPx(s, pxPerSecond) >= minLabelSpacingPx) ??\n TICK_STEPS_US[TICK_STEPS_US.length - 1]\n const minorStep = step / 5\n const first = Math.floor(startUs / step) * step\n const ticks: RulerTick[] = []\n for (let us = first; us <= endUs + step; us += minorStep) {\n if (us < 0) continue\n ticks.push({\n us: us as Us,\n px: usToPx(us, pxPerSecond),\n major: us % step === 0,\n })\n }\n return ticks\n}\n\n/** 保证值落在 [min, max] */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n"],"mappings":";AAmBA,IAAa,gBAAgB;AAC7B,IAAa,YAAY;;AAGzB,SAAgB,QAAQ,KAAiB;CACvC,OAAO,KAAK,MAAM,MAAM,aAAa;AACvC;;AAGA,SAAgB,QAAQ,IAAoB;CAC1C,OAAO,KAAK;AACd;;AAGA,SAAgB,OAAO,IAAgB;CACrC,OAAO,KAAK,MAAM,KAAK,SAAS;AAClC;;AAGA,SAAgB,OAAO,IAAgB;CACrC,OAAO,KAAK,MAAM,KAAK,SAAS;AAClC;;AAGA,SAAgB,KAAK,GAAe;CAClC,OAAO,KAAK,MAAM,CAAC;AACrB;;AAGA,SAAgB,KAAK,GAAe;CAClC,OAAO;AACT;;;;;;AAOA,SAAgB,OAAO,IAAY,aAAyB;CAC1D,OAAS,KAAK,gBAAiB;AACjC;;;;;;AAOA,SAAgB,OAAO,IAAY,aAAyB;CAC1D,OAAO,KAAK,MAAO,KAAK,cAAe,aAAa;AACtD;;AAGA,SAAgB,eAAe,IAAY,SAAS,MAAc;CAEhE,MAAM,UAAU,KAAK,MADR,KAAK,IAAI,GAAG,EACE,IAAO,GAAI;CACtC,MAAM,KAAK,UAAU;CACrB,MAAM,WAAW,KAAK,MAAM,UAAU,GAAI;CAC1C,MAAM,MAAM,WAAW;CACvB,MAAM,MAAM,KAAK,MAAM,WAAW,EAAE,IAAI;CACxC,MAAM,OAAO,KAAK,MAAM,WAAW,IAAI;CACvC,MAAM,OAAO,GAAW,MAAM,MAAM,OAAO,CAAC,CAAC,CAAC,SAAS,KAAK,GAAG;CAC/D,MAAM,OAAO,GAAG,IAAI,IAAI,EAAE,GAAG,IAAI,GAAG,EAAE,GAAG,IAAI,GAAG;CAChD,OAAO,SAAS,GAAG,KAAK,GAAG,IAAI,IAAI,CAAC,MAAM;AAC5C;;AAGA,SAAgB,eAAe,IAAoB;CACjD,IAAI,CAAC,OAAO,SAAS,EAAE,GAAG,OAAO;CACjC,MAAM,WAAW,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,aAAa,CAAC;CAC3D,MAAM,MAAM,WAAW;CACvB,MAAM,MAAM,KAAK,MAAM,WAAW,EAAE,IAAI;CACxC,MAAM,OAAO,KAAK,MAAM,WAAW,IAAI;CACvC,MAAM,OAAO,MAAc,OAAO,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;CACpD,OAAO,OAAO,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,EAAE,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,EAAE,GAAG,IAAI,GAAG;AAC9E;AAQA,IAAM,gBAAgB;CACpB;CAAS;CAAS;CAAS;CAAW;CAAW;CAAW;CAAY;CACxE;CAAY;CAAY;CAAa;CAAa;AACpD;;;;;;;;;AAUA,SAAgB,WACd,aACA,iBACA,eAAe,GACF;CACb,MAAM,UAAU,OAAO,cAAc,WAAW;CAChD,MAAM,QAAQ,OAAO,eAAe,iBAAiB,WAAW;CAChE,MAAM,oBAAoB;CAC1B,MAAM,OACJ,cAAc,MAAM,MAAM,OAAO,GAAG,WAAW,KAAK,iBAAiB,KACrE,cAAc,cAAc,SAAS;CACvC,MAAM,YAAY,OAAO;CACzB,MAAM,QAAQ,KAAK,MAAM,UAAU,IAAI,IAAI;CAC3C,MAAM,QAAqB,CAAC;CAC5B,KAAK,IAAI,KAAK,OAAO,MAAM,QAAQ,MAAM,MAAM,WAAW;EACxD,IAAI,KAAK,GAAG;EACZ,MAAM,KAAK;GACL;GACJ,IAAI,OAAO,IAAI,WAAW;GAC1B,OAAO,KAAK,SAAS;EACvB,CAAC;CACH;CACA,OAAO;AACT;;AAGA,SAAgB,MAAM,OAAe,KAAa,KAAqB;CACrE,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC3C"}
|
|
@@ -1,19 +1,37 @@
|
|
|
1
|
-
const
|
|
1
|
+
const require_id = require("./id-DaCxD4-5.cjs");
|
|
2
|
+
const require_time = require("./time-CYEd91qN.cjs");
|
|
3
|
+
const require_canvas_size = require("./canvas-size-DIABzEW2.cjs");
|
|
2
4
|
let zustand_vanilla = require("zustand/vanilla");
|
|
3
5
|
let immer = require("immer");
|
|
4
6
|
// src/project/schema.ts
|
|
7
|
+
/**
|
|
8
|
+
* 生成可落盘的工程快照。
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ 必须是**深拷贝**:返回值会交给宿主(写入本地 JSON / 走 IPC)。
|
|
11
|
+
* 若直接返回 store 里的对象引用,宿主对这些字段的任何改动都会反向污染编辑器状态,
|
|
12
|
+
* 异步写盘期间发生编辑也会让「存下去的内容」与「保存时刻的状态」不一致。
|
|
13
|
+
*/
|
|
5
14
|
function serializeProject(project, assets) {
|
|
6
15
|
return {
|
|
7
16
|
schemaVersion: 1,
|
|
8
17
|
savedAt: Date.now(),
|
|
9
|
-
project,
|
|
10
|
-
assets: assets.map(stripRuntimeFields)
|
|
18
|
+
project: structuredClone(project),
|
|
19
|
+
assets: assets.map((asset) => stripRuntimeFields(asset))
|
|
11
20
|
};
|
|
12
21
|
}
|
|
13
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* 去掉不可持久化 / 无意义的运行时字段。
|
|
24
|
+
*
|
|
25
|
+
* - `thumbnailUrls`:会话内的 `blob:` URL,刷新后必然失效 —— 存下来只会让
|
|
26
|
+
* 恢复后的卡片显示破图,不如不存(恢复时重抽);
|
|
27
|
+
* - `waveform`:纯派生数据,400 个浮点数 ≈ 4KB。一个 20 条素材的工程光波形
|
|
28
|
+
* 就多占 80KB,而找回源文件后重算一次即可。
|
|
29
|
+
*
|
|
30
|
+
* 其余字段(含宿主写入的 `sourcePath`)原样保留。
|
|
31
|
+
*/
|
|
14
32
|
function stripRuntimeFields(asset) {
|
|
15
|
-
const { thumbnailUrls: _thumbnailUrls, ...rest } = asset;
|
|
16
|
-
return
|
|
33
|
+
const { thumbnailUrls: _thumbnailUrls, waveform: _waveform, ...rest } = asset;
|
|
34
|
+
return structuredClone(rest);
|
|
17
35
|
}
|
|
18
36
|
var ProjectSchemaError = class extends Error {
|
|
19
37
|
constructor(message) {
|
|
@@ -26,16 +44,18 @@ function deserializeProject(raw) {
|
|
|
26
44
|
const data = raw;
|
|
27
45
|
if (typeof data.schemaVersion !== "number") throw new ProjectSchemaError("工程缺少 schemaVersion,无法判断版本");
|
|
28
46
|
if (data.schemaVersion > 1) throw new ProjectSchemaError(`工程版本 ${data.schemaVersion} 高于当前应用支持的 1,请升级应用`);
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
31
|
-
const
|
|
47
|
+
const migrated = migrate(data.schemaVersion, data.project);
|
|
48
|
+
if (migrated == null) throw new ProjectSchemaError("工程主体缺失");
|
|
49
|
+
const project = require_canvas_size.normalizeProject(migrated);
|
|
50
|
+
if (project == null) throw new ProjectSchemaError("工程结构无法识别");
|
|
51
|
+
const assets = (Array.isArray(data.assets) ? data.assets : []).map((asset) => require_canvas_size.normalizeAsset(asset)).filter((asset) => asset != null);
|
|
32
52
|
const assetIds = new Set(assets.map((a) => a.id));
|
|
33
|
-
const fileBacked = new Set(assets.filter((a) => a.kind === "video" || a.kind === "audio" || a.kind === "image").map((a) => a.id));
|
|
34
53
|
const referenced = new Set(project.clips.map((c) => c.assetId));
|
|
54
|
+
const fileBacked = assets.filter((a) => (a.kind === "video" || a.kind === "audio" || a.kind === "image") && referenced.has(a.id));
|
|
35
55
|
return {
|
|
36
56
|
project,
|
|
37
57
|
assets,
|
|
38
|
-
missingAssetIds: Array.from(/* @__PURE__ */ new Set([...project.clips.map((c) => c.assetId).filter((id) => !assetIds.has(id)), ...
|
|
58
|
+
missingAssetIds: Array.from(/* @__PURE__ */ new Set([...project.clips.map((c) => c.assetId).filter((id) => !assetIds.has(id)), ...fileBacked.map((a) => a.id)]))
|
|
39
59
|
};
|
|
40
60
|
}
|
|
41
61
|
function migrate(version, project) {
|
|
@@ -188,6 +208,69 @@ function createWebBridge(options = {}) {
|
|
|
188
208
|
}
|
|
189
209
|
};
|
|
190
210
|
}
|
|
211
|
+
// src/export/types.ts
|
|
212
|
+
/** 导出默认值:1080p / 30fps / 8Mbps / 含音轨 */
|
|
213
|
+
var DEFAULT_EXPORT_SETTINGS = {
|
|
214
|
+
preset: "1080p",
|
|
215
|
+
width: 1920,
|
|
216
|
+
height: 1080,
|
|
217
|
+
fps: 30,
|
|
218
|
+
bitrate: 8e6,
|
|
219
|
+
includeAudio: true
|
|
220
|
+
};
|
|
221
|
+
function even(n) {
|
|
222
|
+
return Math.max(2, Math.round(n / 2) * 2);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* 把宿主传入的局部设置补全为完整设置。
|
|
226
|
+
*
|
|
227
|
+
* 只给了 `preset` 时按预设推导尺寸(`source` 取工程尺寸并向下对齐到偶数),
|
|
228
|
+
* 显式给了 `width` / `height` 则以显式值为准。
|
|
229
|
+
*/
|
|
230
|
+
function resolveExportSettings(project, partial) {
|
|
231
|
+
const preset = partial?.preset ?? DEFAULT_EXPORT_SETTINGS.preset;
|
|
232
|
+
const presetSize = preset === "720p" ? {
|
|
233
|
+
width: 1280,
|
|
234
|
+
height: 720
|
|
235
|
+
} : preset === "1080p" ? {
|
|
236
|
+
width: 1920,
|
|
237
|
+
height: 1080
|
|
238
|
+
} : {
|
|
239
|
+
width: even(project.width),
|
|
240
|
+
height: even(project.height)
|
|
241
|
+
};
|
|
242
|
+
return {
|
|
243
|
+
preset,
|
|
244
|
+
width: partial?.width ?? presetSize.width,
|
|
245
|
+
height: partial?.height ?? presetSize.height,
|
|
246
|
+
fps: partial?.fps ?? DEFAULT_EXPORT_SETTINGS.fps,
|
|
247
|
+
bitrate: partial?.bitrate ?? DEFAULT_EXPORT_SETTINGS.bitrate,
|
|
248
|
+
includeAudio: partial?.includeAudio ?? DEFAULT_EXPORT_SETTINGS.includeAudio
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/** 默认文件名(与编辑器无关,宿主可覆盖) */
|
|
252
|
+
function createExportFilename(date = /* @__PURE__ */ new Date()) {
|
|
253
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
254
|
+
return `cut-test-${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}.mp4`;
|
|
255
|
+
}
|
|
256
|
+
// src/errors.ts
|
|
257
|
+
/**
|
|
258
|
+
* 对外错误类型。
|
|
259
|
+
*
|
|
260
|
+
* 放在根入口(不依赖 WebAV / React),宿主可以 `instanceof` 判断后决定
|
|
261
|
+
* 是否弹错误框 —— 用户主动取消导出不是错误,不该被当成失败提示。
|
|
262
|
+
*/
|
|
263
|
+
/** 导出被 `cancel()` 取消。 */
|
|
264
|
+
var ExportCancelledError = class extends Error {
|
|
265
|
+
constructor(message = "导出已取消") {
|
|
266
|
+
super(message);
|
|
267
|
+
this.name = "ExportCancelledError";
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
/** 判断任意异常是否是「用户取消导出」。 */
|
|
271
|
+
function isExportCancelled(error) {
|
|
272
|
+
return error instanceof ExportCancelledError;
|
|
273
|
+
}
|
|
191
274
|
// src/capability.ts
|
|
192
275
|
/**
|
|
193
276
|
* 运行环境能力探测。
|
|
@@ -493,23 +576,17 @@ var immer$1 = immerImpl;
|
|
|
493
576
|
function createEditorStore(options) {
|
|
494
577
|
const history = options.history;
|
|
495
578
|
return (0, zustand_vanilla.createStore)()(immer$1((set, get) => {
|
|
496
|
-
/**
|
|
497
|
-
* 在结构性变更前压栈(必须在 set 之前调用,拿到的是「变更前」的完整快照)。
|
|
498
|
-
*
|
|
499
|
-
* @param label 变更描述
|
|
500
|
-
* @param mergeKey 合并键:连续同质操作(如拖动同一个滑块)在时间窗口内合并为一步撤销。
|
|
501
|
-
* `record()` 内部只保留第一次的快照,因此一次拖动 = 一步 Ctrl+Z。
|
|
502
|
-
*/
|
|
579
|
+
/** 在结构性变更前压栈(必须在 set 之前调用,拿到的是「变更前」的完整快照) */
|
|
503
580
|
function recordHistory(label, mergeKey) {
|
|
504
581
|
const { project, assets } = get();
|
|
505
582
|
history.getState().record({
|
|
506
583
|
project: structuredClone(project),
|
|
507
584
|
assets: structuredClone(assets),
|
|
508
585
|
label
|
|
509
|
-
}, {
|
|
586
|
+
}, mergeKey != null ? {
|
|
510
587
|
label,
|
|
511
588
|
mergeKey
|
|
512
|
-
});
|
|
589
|
+
} : { label });
|
|
513
590
|
}
|
|
514
591
|
return {
|
|
515
592
|
project: options.project ?? require_canvas_size.createEmptyProject(),
|
|
@@ -548,6 +625,18 @@ function createEditorStore(options) {
|
|
|
548
625
|
draft.dirty = true;
|
|
549
626
|
});
|
|
550
627
|
},
|
|
628
|
+
setProjectId: (id) => {
|
|
629
|
+
if (id === get().project.id) return;
|
|
630
|
+
recordHistory("修改工程 ID");
|
|
631
|
+
set((draft) => {
|
|
632
|
+
draft.project.id = id;
|
|
633
|
+
draft.project.updatedAt = Date.now();
|
|
634
|
+
draft.dirty = true;
|
|
635
|
+
});
|
|
636
|
+
},
|
|
637
|
+
setMissingAssetIds: (ids) => set((draft) => {
|
|
638
|
+
draft.missingAssetIds = [...ids];
|
|
639
|
+
}),
|
|
551
640
|
/**
|
|
552
641
|
* 修改画布尺寸(比例)。
|
|
553
642
|
*
|
|
@@ -666,7 +755,7 @@ function createEditorStore(options) {
|
|
|
666
755
|
const clip = draft.project.clips.find((c) => c.id === clipId);
|
|
667
756
|
if (!clip) return;
|
|
668
757
|
clip.trackId = trackId;
|
|
669
|
-
clip.startUs =
|
|
758
|
+
clip.startUs = require_time.asUs(Math.max(0, startUs));
|
|
670
759
|
draft.project.updatedAt = Date.now();
|
|
671
760
|
draft.dirty = true;
|
|
672
761
|
});
|
|
@@ -692,16 +781,16 @@ function createEditorStore(options) {
|
|
|
692
781
|
const sourceOffset = Math.round(leftDuration * clip.playbackRate);
|
|
693
782
|
const right = {
|
|
694
783
|
...structuredClone(clip),
|
|
695
|
-
id:
|
|
696
|
-
startUs:
|
|
697
|
-
durationUs:
|
|
698
|
-
inPointUs:
|
|
699
|
-
fadeInUs:
|
|
784
|
+
id: require_id.createId("clip"),
|
|
785
|
+
startUs: require_time.asUs(atUs),
|
|
786
|
+
durationUs: require_time.asUs(rightDuration),
|
|
787
|
+
inPointUs: require_time.asUs(clip.inPointUs + sourceOffset),
|
|
788
|
+
fadeInUs: require_time.asUs(Math.min(clip.fadeInUs, rightDuration))
|
|
700
789
|
};
|
|
701
790
|
return {
|
|
702
791
|
leftId: clip.id,
|
|
703
|
-
leftDuration:
|
|
704
|
-
leftFadeOut:
|
|
792
|
+
leftDuration: require_time.asUs(leftDuration),
|
|
793
|
+
leftFadeOut: require_time.asUs(Math.min(clip.fadeOutUs, leftDuration)),
|
|
705
794
|
right
|
|
706
795
|
};
|
|
707
796
|
});
|
|
@@ -727,7 +816,7 @@ function createEditorStore(options) {
|
|
|
727
816
|
const ids = state.selectedClipIds;
|
|
728
817
|
const created = [];
|
|
729
818
|
if (ids.length === 0) return created;
|
|
730
|
-
const pairs = require_canvas_size.duplicateClipsData(structuredClone(state.project.clips), ids, offsetUs, () =>
|
|
819
|
+
const pairs = require_canvas_size.duplicateClipsData(structuredClone(state.project.clips), ids, offsetUs, () => require_id.createId("clip"));
|
|
731
820
|
recordHistory("复制片段");
|
|
732
821
|
set((draft) => {
|
|
733
822
|
for (const { sourceId, clip } of pairs) {
|
|
@@ -788,7 +877,7 @@ function createEditorStore(options) {
|
|
|
788
877
|
draft.selectedAssetId = id;
|
|
789
878
|
}),
|
|
790
879
|
setPlayhead: (us) => set((draft) => {
|
|
791
|
-
draft.playheadUs =
|
|
880
|
+
draft.playheadUs = require_time.asUs(Math.max(0, us));
|
|
792
881
|
}),
|
|
793
882
|
setPlaying: (playing) => set((draft) => {
|
|
794
883
|
draft.playing = playing;
|
|
@@ -894,12 +983,24 @@ function normalizeWaveform(peaks, length) {
|
|
|
894
983
|
}
|
|
895
984
|
return out;
|
|
896
985
|
}
|
|
986
|
+
Object.defineProperty(exports, "DEFAULT_EXPORT_SETTINGS", {
|
|
987
|
+
enumerable: true,
|
|
988
|
+
get: function() {
|
|
989
|
+
return DEFAULT_EXPORT_SETTINGS;
|
|
990
|
+
}
|
|
991
|
+
});
|
|
897
992
|
Object.defineProperty(exports, "ENGINE_DEPENDENCIES_INSTALL_COMMAND", {
|
|
898
993
|
enumerable: true,
|
|
899
994
|
get: function() {
|
|
900
995
|
return ENGINE_DEPENDENCIES_INSTALL_COMMAND;
|
|
901
996
|
}
|
|
902
997
|
});
|
|
998
|
+
Object.defineProperty(exports, "ExportCancelledError", {
|
|
999
|
+
enumerable: true,
|
|
1000
|
+
get: function() {
|
|
1001
|
+
return ExportCancelledError;
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
903
1004
|
Object.defineProperty(exports, "ProjectSchemaError", {
|
|
904
1005
|
enumerable: true,
|
|
905
1006
|
get: function() {
|
|
@@ -930,6 +1031,12 @@ Object.defineProperty(exports, "createEventBus", {
|
|
|
930
1031
|
return createEventBus;
|
|
931
1032
|
}
|
|
932
1033
|
});
|
|
1034
|
+
Object.defineProperty(exports, "createExportFilename", {
|
|
1035
|
+
enumerable: true,
|
|
1036
|
+
get: function() {
|
|
1037
|
+
return createExportFilename;
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
933
1040
|
Object.defineProperty(exports, "createHistoryStore", {
|
|
934
1041
|
enumerable: true,
|
|
935
1042
|
get: function() {
|
|
@@ -990,12 +1097,24 @@ Object.defineProperty(exports, "isEnvironmentUsable", {
|
|
|
990
1097
|
return isEnvironmentUsable;
|
|
991
1098
|
}
|
|
992
1099
|
});
|
|
1100
|
+
Object.defineProperty(exports, "isExportCancelled", {
|
|
1101
|
+
enumerable: true,
|
|
1102
|
+
get: function() {
|
|
1103
|
+
return isExportCancelled;
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
993
1106
|
Object.defineProperty(exports, "normalizeWaveform", {
|
|
994
1107
|
enumerable: true,
|
|
995
1108
|
get: function() {
|
|
996
1109
|
return normalizeWaveform;
|
|
997
1110
|
}
|
|
998
1111
|
});
|
|
1112
|
+
Object.defineProperty(exports, "resolveExportSettings", {
|
|
1113
|
+
enumerable: true,
|
|
1114
|
+
get: function() {
|
|
1115
|
+
return resolveExportSettings;
|
|
1116
|
+
}
|
|
1117
|
+
});
|
|
999
1118
|
Object.defineProperty(exports, "selectAssetById", {
|
|
1000
1119
|
enumerable: true,
|
|
1001
1120
|
get: function() {
|
|
@@ -1063,4 +1182,4 @@ Object.defineProperty(exports, "summarizeCapabilities", {
|
|
|
1063
1182
|
}
|
|
1064
1183
|
});
|
|
1065
1184
|
|
|
1066
|
-
//# sourceMappingURL=waveform-
|
|
1185
|
+
//# sourceMappingURL=waveform-BYwDdKpC.cjs.map
|