dsh-drawai 0.4.0 → 0.4.2
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/LICENSE +430 -418
- package/README.md +96 -249
- package/lib/audio-media.js +5 -2
- package/lib/client.js +45 -12
- package/lib/drawing-runner.js +6 -4
- package/lib/ffmpeg-manager.js +1056 -0
- package/lib/index.js +7719 -7396
- package/lib/motion/apply.js +4 -3
- package/lib/motion/asset-pack.js +394 -335
- package/lib/motion/assets.js +4 -3
- package/lib/motion/beats.js +3 -2
- package/lib/motion/collect.js +3 -1
- package/lib/motion/delivery.js +29 -1
- package/lib/motion/demo-assets-lock.js +10 -0
- package/lib/motion/ffmpeg.js +5 -4
- package/lib/motion/jobs.js +5 -1
- package/lib/motion/mgstyles/vendor/README.md +28 -0
- package/lib/motion/mgstyles/vendor/modules/d3/LICENSE +13 -0
- package/lib/motion/mgstyles/vendor/modules/d3/dist/d3.min.js +2 -0
- package/lib/motion/mgstyles/vendor/modules/opentype.js/LICENSE +20 -0
- package/lib/motion/mgstyles/vendor/modules/opentype.js/dist/opentype.mjs +14460 -0
- package/lib/motion/mgstyles/vendor/modules/simplex-noise/LICENSE +21 -0
- package/lib/motion/mgstyles/vendor/modules/simplex-noise/dist/esm/simplex-noise.js +466 -0
- package/lib/motion/mgstyles/vendor/modules/topojson-client/LICENSE +13 -0
- package/lib/motion/mgstyles/vendor/modules/topojson-client/dist/topojson-client.min.js +2 -0
- package/lib/motion/preview.js +13 -4
- package/lib/motion/render.js +179 -159
- package/lib/motion/runtime/package-lock.json +3058 -0
- package/lib/motion/runtime/package.json +16 -0
- package/lib/motion/style-frames.js +306 -268
- package/lib/motion/sync.js +214 -214
- package/lib/motion/video-runtime.js +192 -0
- package/lib/motion-capture.js +2 -1
- package/lib/motion-film-review.js +2 -2
- package/lib/motion-project.js +105 -79
- package/lib/motion-render-worker.js +174 -144
- package/lib/motion-vision.js +3 -2
- package/lib/routing/router.js +222 -211
- package/lib/routing/vendor/README.md +96 -96
- package/lib/routing/vendor/libavoid-routing.js +753 -753
- package/lib/routing/vendor/libavoid.min.js +59 -59
- package/lib/shotcard-reference.js +2 -2
- package/lib/style-kernel.js +16 -6
- package/lib/video-export.js +32 -20
- package/package.json +17 -12
|
@@ -4,7 +4,7 @@ import { join } from 'node:path'
|
|
|
4
4
|
import { createHash } from 'node:crypto'
|
|
5
5
|
import { execFile } from 'node:child_process'
|
|
6
6
|
import { promisify } from 'node:util'
|
|
7
|
-
import {
|
|
7
|
+
import { ensureFfmpeg } from './ffmpeg-manager.js'
|
|
8
8
|
import { motionCardContract } from './motion-library.js'
|
|
9
9
|
const exec = promisify(execFile)
|
|
10
10
|
|
|
@@ -30,7 +30,7 @@ export async function prepareReferenceFrames(composition, directory, options = {
|
|
|
30
30
|
}
|
|
31
31
|
writeFileSync(video + '.tmp', Buffer.concat(chunks)); renameSync(video + '.tmp', video)
|
|
32
32
|
}
|
|
33
|
-
const binary = options.ffmpegPath ||
|
|
33
|
+
const binary = options.ffmpegPath || await ensureFfmpeg({ signal: options.signal })
|
|
34
34
|
let info = ''
|
|
35
35
|
try { const probe = await exec(binary, ['-hide_banner', '-i', video], { windowsHide: true, timeout: 15000, signal: options.signal }); info = probe.stderr }
|
|
36
36
|
catch (error) { info = error.stderr || ''; if (!info) throw error }
|
package/lib/style-kernel.js
CHANGED
|
@@ -152,9 +152,16 @@ var DASH_PRESETS = {
|
|
|
152
152
|
/** 线型枚举顺序。 */
|
|
153
153
|
var DASH_KINDS = ['solid', 'dashed', 'dotted']
|
|
154
154
|
|
|
155
|
-
/**
|
|
155
|
+
/**
|
|
156
|
+
* 箭头枚举 → style 键。
|
|
157
|
+
*
|
|
158
|
+
* `none` 必须**显式写 `endArrow=none`**:drawio 的 defaultEdge 自带
|
|
159
|
+
* `endArrow=classic`(`mxStylesheet.createDefaultEdgeStyle`),删键只会让这条边
|
|
160
|
+
* 回落到缺省,箭头反而又长出来。`=none` 才是在合并后的样式里把这个键删掉
|
|
161
|
+
* (`mxStylesheet.getCellStyle`:value 为 `none` 时 delete),从而真的不画。
|
|
162
|
+
*/
|
|
156
163
|
var ARROW_PRESETS = {
|
|
157
|
-
none:
|
|
164
|
+
none: { endArrow: 'none' },
|
|
158
165
|
end: { endArrow: 'classic' },
|
|
159
166
|
both: { endArrow: 'classic', startArrow: 'classic' },
|
|
160
167
|
start: { startArrow: 'classic' },
|
|
@@ -655,7 +662,10 @@ function arrowValueIsSet(value) {
|
|
|
655
662
|
|
|
656
663
|
/** style → 箭头枚举。 */
|
|
657
664
|
function arrowFromStyle(style) {
|
|
658
|
-
|
|
665
|
+
// 按 drawio 的**有效样式**算:mxStylesheet.getCellStyle 会先把 defaultEdge 合进来,
|
|
666
|
+
// 而 defaultEdge 自带 endArrow=classic —— 所以缺键不是"没有箭头",是有 classic。
|
|
667
|
+
// startArrow 不在 defaultEdge 里,缺键才是真的没有。
|
|
668
|
+
var end = arrowValueIsSet(styleGet(style, 'endArrow', 'classic'))
|
|
659
669
|
var start = arrowValueIsSet(styleGet(style, 'startArrow', null))
|
|
660
670
|
if (end && start) return 'both'
|
|
661
671
|
if (end) return 'end'
|
|
@@ -663,12 +673,12 @@ function arrowFromStyle(style) {
|
|
|
663
673
|
return 'none'
|
|
664
674
|
}
|
|
665
675
|
|
|
666
|
-
/** 箭头枚举 → style。none
|
|
676
|
+
/** 箭头枚举 → style。none 写 `endArrow=none`;null/undefined 才是"删键回落到 drawio 缺省"。 */
|
|
667
677
|
function styleWithArrow(style, kind) {
|
|
668
678
|
var cleared = stylePatch(style, { endArrow: null, startArrow: null })
|
|
669
|
-
if (kind === null || kind === undefined
|
|
679
|
+
if (kind === null || kind === undefined) return cleared
|
|
670
680
|
var preset = ARROW_PRESETS[kind]
|
|
671
|
-
if (preset === undefined
|
|
681
|
+
if (preset === undefined) return cleared
|
|
672
682
|
return stylePatch(cleared, preset)
|
|
673
683
|
}
|
|
674
684
|
|
package/lib/video-export.js
CHANGED
|
@@ -6,9 +6,9 @@ import { Worker } from 'node:worker_threads'
|
|
|
6
6
|
import { copyFile } from 'node:fs/promises'
|
|
7
7
|
import { existsSync, mkdirSync, mkdtempSync, rmSync, renameSync, statSync, openSync, readSync, closeSync, realpathSync, readFileSync, writeFileSync, readdirSync } from 'node:fs'
|
|
8
8
|
import { join, relative, isAbsolute, extname, dirname, basename } from 'node:path'
|
|
9
|
-
import { fileURLToPath } from 'node:url'
|
|
10
9
|
import { clipDirFor, readManifest, findSourceFile, sanitizeClipId } from './media.js'
|
|
11
10
|
import { createPerformanceLog } from './performance-log.js'
|
|
11
|
+
import { ensureFfmpeg, ffmpegPathSync } from './ffmpeg-manager.js'
|
|
12
12
|
|
|
13
13
|
function runFfmpeg(binary, args, signal, progress) {
|
|
14
14
|
return new Promise((resolve, reject) => {
|
|
@@ -36,17 +36,14 @@ function runFfmpeg(binary, args, signal, progress) {
|
|
|
36
36
|
})
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/** 同步近似值,只给「还没异步准备过」的旧路径兜底;真正导出走 ensureFfmpeg。 */
|
|
39
40
|
export function ffmpegBinary() {
|
|
40
|
-
|
|
41
|
-
// Development checkout only; published packages use the configured binary or PATH.
|
|
42
|
-
const local = fileURLToPath(new URL('../_local/bin/' + (process.platform === 'win32' ? 'ffmpeg.exe' : 'ffmpeg'), import.meta.url))
|
|
43
|
-
return existsSync(local) ? local : 'ffmpeg'
|
|
41
|
+
return ffmpegPathSync()
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
export function createVideoExportManager(options = {}) {
|
|
47
45
|
const jobs = new Map()
|
|
48
46
|
let closed = false
|
|
49
|
-
const binary = options.ffmpegPath || ffmpegBinary()
|
|
50
47
|
const historyRoot = (path) => join(dirname(path), '.drawai-cache', 'video-export-history')
|
|
51
48
|
const ended = (job) => ['done', 'error', 'interrupted'].includes(job.status)
|
|
52
49
|
function migrationRecords(scope) {
|
|
@@ -60,7 +57,7 @@ export function createVideoExportManager(options = {}) {
|
|
|
60
57
|
}
|
|
61
58
|
function persist(job) {
|
|
62
59
|
const root = historyRoot(job.drawioAbs); mkdirSync(root, { recursive: true })
|
|
63
|
-
const record = Object.fromEntries(['sessionId', 'drawioAbs', 'clip', 'token', 'status', 'time', 'next', 'encoded', 'format', 'kind', 'w', 'h', 'fps', 'count', 'bytes', 'savedPath', 'error'].map((key) => [key, job[key]]))
|
|
60
|
+
const record = Object.fromEntries(['sessionId', 'drawioAbs', 'clip', 'token', 'status', 'time', 'next', 'encoded', 'format', 'kind', 'w', 'h', 'fps', 'count', 'bytes', 'savedPath', 'error', 'message'].map((key) => [key, job[key]]))
|
|
64
61
|
const file = join(root, job.token + '.json')
|
|
65
62
|
writeFileSync(file + '.tmp', JSON.stringify(record)); renameSync(file + '.tmp', file)
|
|
66
63
|
}
|
|
@@ -87,7 +84,7 @@ export function createVideoExportManager(options = {}) {
|
|
|
87
84
|
function view(job) {
|
|
88
85
|
return { token: job.token, status: job.status, count: job.count, received: job.next, encoded: job.encoded,
|
|
89
86
|
clip: job.clip, fps: job.fps, w: job.w, h: job.h, bytes: job.bytes || 0, error: job.error || '', format: job.format,
|
|
90
|
-
background: true, path: job.savedPath || null,
|
|
87
|
+
message: job.message || '', background: true, path: job.savedPath || null,
|
|
91
88
|
performanceEnabled: job.perf.enabled, performanceId: job.perf.id }
|
|
92
89
|
}
|
|
93
90
|
function owned(scope, token) {
|
|
@@ -99,6 +96,7 @@ export function createVideoExportManager(options = {}) {
|
|
|
99
96
|
}
|
|
100
97
|
async function remove(job) {
|
|
101
98
|
jobs.delete(job.token)
|
|
99
|
+
job.removed = true
|
|
102
100
|
job.control.abort()
|
|
103
101
|
if (job.preflight) await job.preflight.catch(() => {})
|
|
104
102
|
if (job.promise) await job.promise.catch(() => {})
|
|
@@ -138,6 +136,26 @@ export function createVideoExportManager(options = {}) {
|
|
|
138
136
|
async function runJob(job) {
|
|
139
137
|
let release
|
|
140
138
|
try {
|
|
139
|
+
// Resolve FFmpeg inside the task: the first use may download it, and a
|
|
140
|
+
// later retry must not keep using a path cached when the host started.
|
|
141
|
+
job.status = 'preparing'
|
|
142
|
+
job.message = '正在准备 FFmpeg…'
|
|
143
|
+
persist(job)
|
|
144
|
+
job.binary = options.ffmpegPath || await ensureFfmpeg({
|
|
145
|
+
signal: job.control.signal,
|
|
146
|
+
onProgress: (event) => { if (event?.message) job.message = event.message },
|
|
147
|
+
})
|
|
148
|
+
if (job.control.signal.aborted) throw new Error('已取消视频导出')
|
|
149
|
+
job.status = 'queued'
|
|
150
|
+
job.message = ''
|
|
151
|
+
persist(job)
|
|
152
|
+
// Probe the actual output path and encoder before uploading any large frames.
|
|
153
|
+
job.preflight = runFfmpeg(job.binary, ['-hide_banner', '-nostdin', '-loglevel', 'error', '-y', '-f', 'lavfi', '-i', 'color=white:s=2x2:r=1',
|
|
154
|
+
'-frames:v', '1', '-c:v', job.format === 'mp4' ? 'libx264' : job.format === 'gif' ? 'gif' : 'libvpx-vp9', '-threads', '1', job.output], job.control.signal)
|
|
155
|
+
const probeDone = job.perf.start('encoderPreflight')
|
|
156
|
+
try { await job.preflight }
|
|
157
|
+
catch (error) { throw new Error('导出预检查失败(目录:' + job.temp + '):' + error.message) }
|
|
158
|
+
finally { probeDone() }
|
|
141
159
|
release = await options.slots?.acquire(job.control.signal)
|
|
142
160
|
job.status = 'rendering'
|
|
143
161
|
persist(job)
|
|
@@ -159,8 +177,10 @@ export function createVideoExportManager(options = {}) {
|
|
|
159
177
|
persist(job)
|
|
160
178
|
job.perf.finish('done')
|
|
161
179
|
} catch (error) {
|
|
162
|
-
|
|
163
|
-
|
|
180
|
+
if (!job.removed) {
|
|
181
|
+
job.status = closed ? 'interrupted' : 'error'; job.error = String(error.message || error); job.message = ''; job.time = Date.now()
|
|
182
|
+
persist(job)
|
|
183
|
+
}
|
|
164
184
|
job.perf.finish(job.control.signal.aborted ? 'cancelled' : 'failed')
|
|
165
185
|
} finally { release?.(); rmSync(job.temp, { recursive: true, force: true }) }
|
|
166
186
|
}
|
|
@@ -199,11 +219,11 @@ export function createVideoExportManager(options = {}) {
|
|
|
199
219
|
paletteArgs.push('-filter_complex', gifWhite + '[opaque]palettegen=max_colors=256:reserve_transparent=0:stats_mode=full[palette]', '-map', '[palette]',
|
|
200
220
|
'-frames:v', '1', '-threads', '1', '-update', '1', join(job.temp, 'palette.png'))
|
|
201
221
|
const paletteDone = job.perf.start('gifPaletteGenerate')
|
|
202
|
-
try { await runFfmpeg(binary, paletteArgs, job.control.signal) }
|
|
222
|
+
try { await runFfmpeg(job.binary, paletteArgs, job.control.signal) }
|
|
203
223
|
finally { paletteDone() }
|
|
204
224
|
}
|
|
205
225
|
const encodeDone = job.perf.start('ffmpegEncode')
|
|
206
|
-
try { await runFfmpeg(binary, args, job.control.signal, (frame) => { job.encoded = frame }) }
|
|
226
|
+
try { await runFfmpeg(job.binary, args, job.control.signal, (frame) => { job.encoded = frame }) }
|
|
207
227
|
finally { encodeDone() }
|
|
208
228
|
if (job.kind === 'svg' && job.encoded !== job.count) throw new Error('输出帧数不完整:预期 ' + job.count + ',实际 ' + job.encoded)
|
|
209
229
|
job.bytes = statSync(job.output).size
|
|
@@ -265,14 +285,6 @@ export function createVideoExportManager(options = {}) {
|
|
|
265
285
|
next: 0, received: 0, encoded: 0, status: 'queued', time: Date.now(), control: new AbortController(), output: join(temp, 'output.' + format) }
|
|
266
286
|
jobs.set(job.token, job)
|
|
267
287
|
persist(job)
|
|
268
|
-
// Probe the actual output path and encoder before uploading any large frames.
|
|
269
|
-
job.preflight = runFfmpeg(binary, ['-hide_banner', '-nostdin', '-loglevel', 'error', '-y', '-f', 'lavfi', '-i', 'color=white:s=2x2:r=1',
|
|
270
|
-
'-frames:v', '1', '-c:v', format === 'mp4' ? 'libx264' : format === 'gif' ? 'gif' : 'libvpx-vp9', '-threads', '1', job.output], job.control.signal)
|
|
271
|
-
const probeDone = perf.start('encoderPreflight')
|
|
272
|
-
try { await job.preflight }
|
|
273
|
-
catch (error) { throw new Error('导出预检查失败(目录:' + temp + '):' + error.message) }
|
|
274
|
-
finally { probeDone() }
|
|
275
|
-
if (closed || job.control.signal.aborted) throw new Error('视频导出服务已关闭')
|
|
276
288
|
job.promise = runJob(job)
|
|
277
289
|
return view(job)
|
|
278
290
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-drawai",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DSH 右侧栏的可编辑画布 + AI 语义绘图工具(diagram_apply / diagram_read),直接读写原生 .drawio 文件",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,10 +22,12 @@
|
|
|
22
22
|
"test:audio-ui": "node tools/check-audio-ui.mjs",
|
|
23
23
|
"watch": "node tools/watch.mjs",
|
|
24
24
|
"check": "node tools/check-package.mjs",
|
|
25
|
-
"test": "node tools/check-media-package.mjs && node tools/check-motion-session.mjs && node tools/check-motion-host.mjs && node tools/check-motion-canvas-spec.mjs && node tools/check-motion-json-bom.mjs && node tools/check-motion-pilot-host.mjs && node tools/check-motion-boundaries.mjs && node tools/check-motion-cache-fetch.mjs && node tools/check-media-library-host.mjs && node tools/check-motion-ui.mjs && node tools/check-native-canvas.mjs && node tools/check-adaptive-palette.mjs && node tools/check-auto-save.mjs && node tools/check-drawing-queue.mjs && node tools/check-background-tasks.mjs && node tools/check-background-ui.mjs && node tools/check-trace-optimization.mjs && node tools/check-performance.mjs && node tools/check-mxfile.mjs && node tools/check-routing.mjs && node tools/check-route-preview.mjs && node tools/check-host.mjs && node tools/check-render.mjs && node tools/check-component.mjs && node tools/check-text-copy.mjs && node tools/check-image-export.mjs && node tools/check-canvas-sessions.mjs && node tools/check-trace.mjs && node tools/check-media.mjs && node tools/check-video-player.mjs && node tools/check-image-trace.mjs && node tools/check-video-trace.mjs && node tools/check-video-reuse.mjs && node tools/check-video-audio.mjs && node tools/check-video-export.mjs && node tools/check-video-import.mjs && node tools/check-node-labels.mjs && node tools/check-imagegen-disabled.mjs && node tools/check-imagegen.mjs",
|
|
25
|
+
"test": "node tools/check-video-runtime.mjs && node tools/check-ffmpeg-manager.mjs && node tools/check-media-package.mjs && node tools/check-motion-session.mjs && node tools/check-motion-host.mjs && node tools/check-motion-canvas-spec.mjs && node tools/check-motion-json-bom.mjs && node tools/check-motion-pilot-host.mjs && node tools/check-motion-boundaries.mjs && node tools/check-motion-cache-fetch.mjs && node tools/check-media-library-host.mjs && node tools/check-motion-ui.mjs && node tools/check-native-canvas.mjs && node tools/check-adaptive-palette.mjs && node tools/check-auto-save.mjs && node tools/check-drawing-queue.mjs && node tools/check-background-tasks.mjs && node tools/check-background-ui.mjs && node tools/check-trace-optimization.mjs && node tools/check-performance.mjs && node tools/check-mxfile.mjs && node tools/check-routing.mjs && node tools/check-route-preview.mjs && node tools/check-host.mjs && node tools/check-render.mjs && node tools/check-component.mjs && node tools/check-text-copy.mjs && node tools/check-image-export.mjs && node tools/check-canvas-sessions.mjs && node tools/check-trace.mjs && node tools/check-media.mjs && node tools/check-video-player.mjs && node tools/check-image-trace.mjs && node tools/check-video-trace.mjs && node tools/check-video-reuse.mjs && node tools/check-video-audio.mjs && node tools/check-video-export.mjs && node tools/check-video-import.mjs && node tools/check-node-labels.mjs && node tools/check-imagegen-disabled.mjs && node tools/check-imagegen.mjs",
|
|
26
26
|
"bench:performance": "node tools/benchmark-performance.mjs",
|
|
27
27
|
"test:shotcards-render": "node tools/check-shotcard-render.mjs",
|
|
28
|
-
"test:shotcards-ui": "node tools/check-shotcards-ui.mjs"
|
|
28
|
+
"test:shotcards-ui": "node tools/check-shotcards-ui.mjs",
|
|
29
|
+
"test:video-runtime": "node tools/check-video-runtime.mjs",
|
|
30
|
+
"test:ffmpeg": "node tools/check-ffmpeg-manager.mjs"
|
|
29
31
|
},
|
|
30
32
|
"exports": {
|
|
31
33
|
".": "./lib/index.js",
|
|
@@ -54,18 +56,10 @@
|
|
|
54
56
|
"dependencies": {
|
|
55
57
|
"@babel/parser": "7.28.5",
|
|
56
58
|
"@babel/traverse": "7.28.5",
|
|
57
|
-
"@react-three/fiber": "9.5.0",
|
|
58
|
-
"@remotion/bundler": "4.0.484",
|
|
59
|
-
"@remotion/motion-blur": "4.0.484",
|
|
60
|
-
"@remotion/renderer": "4.0.484",
|
|
61
|
-
"@remotion/three": "4.0.484",
|
|
62
59
|
"@resvg/resvg-js": "^2.6.2",
|
|
63
60
|
"parse5": "7.3.0",
|
|
64
61
|
"postcss": "8.5.32",
|
|
65
|
-
"
|
|
66
|
-
"react-dom": "19.2.7",
|
|
67
|
-
"remotion": "4.0.484",
|
|
68
|
-
"three": "0.183.2"
|
|
62
|
+
"npm": "10.9.4"
|
|
69
63
|
},
|
|
70
64
|
"peerDependencies": {
|
|
71
65
|
"@deepseek-ai/dsh-tools": ">=0.1.0-rc.1 <0.1.0 || >=0.1.5-rc.1 <0.2.0-0 || >=0.2.0-rc.1 <0.3.0-0"
|
|
@@ -82,5 +76,16 @@
|
|
|
82
76
|
"@deepseek-ai/dsh-api-workspace-files"
|
|
83
77
|
]
|
|
84
78
|
}
|
|
79
|
+
},
|
|
80
|
+
"devDependencies": {
|
|
81
|
+
"@react-three/fiber": "9.5.0",
|
|
82
|
+
"@remotion/bundler": "4.0.484",
|
|
83
|
+
"@remotion/motion-blur": "4.0.484",
|
|
84
|
+
"@remotion/renderer": "4.0.484",
|
|
85
|
+
"@remotion/three": "4.0.484",
|
|
86
|
+
"react": "19.2.7",
|
|
87
|
+
"react-dom": "19.2.7",
|
|
88
|
+
"remotion": "4.0.484",
|
|
89
|
+
"three": "0.183.2"
|
|
85
90
|
}
|
|
86
91
|
}
|