dsh-ffmpeg 0.2.0 → 0.3.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/LICENSE +21 -0
- package/README.en.md +14 -1
- package/README.md +14 -2
- package/lib/args.d.ts +8 -0
- package/lib/args.js +7 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/tools.d.ts +4 -1
- package/lib/tools.js +135 -5
- package/package.json +60 -61
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 stardustlc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
DSH (DeepSeek Harness) video-processing plugin: seven tools covering probing, cutting, concatenation, transcoding, subtitles, extraction and GIF creation — all powered by ffmpeg/ffprobe.
|
|
8
8
|
|
|
9
|
+
## Compatibility
|
|
10
|
+
|
|
11
|
+
Verified against `@deepseek-ai/dsh@0.1.1-rc.2` on 2026-08-26. Built for the cordis patch-bundle plugin model (`cordis.patch.yml` + `dsh.bundle.patch`). No runtime imports of `@deepseek-ai/*` internals.
|
|
12
|
+
|
|
9
13
|
## Installation
|
|
10
14
|
|
|
11
15
|
```bash
|
|
@@ -14,6 +18,15 @@ dsh plugin --profile web add dsh-ffmpeg
|
|
|
14
18
|
|
|
15
19
|
ffmpeg must be installed locally (`ffmpeg -version` should work); use `ffmpegPath` / `ffprobePath`, or the `DSH_FFMPEG_PATH` / `DSH_FFPROBE_PATH` environment variables, when it is not on PATH.
|
|
16
20
|
|
|
21
|
+
## Uninstall
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
dsh plugin --profile web remove dsh-ffmpeg
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Then restart the web service. To clean up fully, also remove the plugin entry from your profile `cordis.patch.yml` if you overrode it.
|
|
28
|
+
|
|
29
|
+
|
|
17
30
|
## Configuration
|
|
18
31
|
|
|
19
32
|
Override the plugin row in your profile's `cordis.patch.yml` (defaults apply when absent):
|
|
@@ -67,4 +80,4 @@ pnpm test # build + 57 tests, including a real-ffmpeg end-to-end suite (au
|
|
|
67
80
|
|
|
68
81
|
## License
|
|
69
82
|
|
|
70
|
-
MIT
|
|
83
|
+
MIT
|
package/README.md
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
|
|
12
12
|
DSH(DeepSeek Harness)视频处理工具插件:七个工具覆盖探测、剪辑、拼接、转码、字幕、提取与 GIF 制作,全部由 ffmpeg/ffprobe 完成。
|
|
13
13
|
|
|
14
|
+
## 兼容性
|
|
15
|
+
|
|
16
|
+
在 `@deepseek-ai/dsh@0.1.1-rc.2` 上验证(2026-08-26)。遵循 cordis 组合包补丁模型(`cordis.patch.yml` + `dsh.bundle.patch`),运行时不 import 任何 `@deepseek-ai/*` 内部模块。
|
|
17
|
+
|
|
14
18
|
## 安装
|
|
15
19
|
|
|
16
20
|
```bash
|
|
@@ -19,6 +23,15 @@ dsh plugin --profile web add dsh-ffmpeg
|
|
|
19
23
|
|
|
20
24
|
需要本机已安装 ffmpeg(`ffmpeg -version` 能出结果即可);不在 PATH 上时可用 `ffmpegPath`/`ffprobePath` 显式指定,或设置环境变量 `DSH_FFMPEG_PATH` / `DSH_FFPROBE_PATH`。
|
|
21
25
|
|
|
26
|
+
## 卸载
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
dsh plugin --profile web remove dsh-ffmpeg
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
卸载后重启 Web 服务。如需彻底清理,可再手动删除自己 profile `cordis.patch.yml` 中覆盖的插件行。
|
|
33
|
+
|
|
34
|
+
|
|
22
35
|
## 配置
|
|
23
36
|
|
|
24
37
|
在你自己的 profile 的 `cordis.patch.yml` 里覆盖本插件行(缺省时全部用默认值):
|
|
@@ -72,5 +85,4 @@ pnpm test # 构建 + 57 个测试(含真实 ffmpeg 端到端集成,缺
|
|
|
72
85
|
|
|
73
86
|
## License
|
|
74
87
|
|
|
75
|
-
MIT
|
|
76
|
-
|
|
88
|
+
MIT
|
package/lib/args.d.ts
CHANGED
|
@@ -62,9 +62,17 @@ export interface ExtractSpec {
|
|
|
62
62
|
duration?: number;
|
|
63
63
|
fps?: number;
|
|
64
64
|
streamIndex: number;
|
|
65
|
+
maxFrames?: number;
|
|
65
66
|
}
|
|
66
67
|
/** 提取:音频(拷贝)/ 抽帧序列 / 单帧 / 字幕流。 */
|
|
67
68
|
export declare function extractArgs(ffmpeg: string, spec: ExtractSpec): string[];
|
|
69
|
+
/** 定点抽帧:在指定时间点取一帧。 */
|
|
70
|
+
export declare function frameAtArgs(ffmpeg: string, spec: {
|
|
71
|
+
input: string;
|
|
72
|
+
time: number;
|
|
73
|
+
output: string;
|
|
74
|
+
overwrite: boolean;
|
|
75
|
+
}): string[];
|
|
68
76
|
export interface GifSpec {
|
|
69
77
|
input: string;
|
|
70
78
|
output: string;
|
package/lib/args.js
CHANGED
|
@@ -91,9 +91,16 @@ export function extractArgs(ffmpeg, spec) {
|
|
|
91
91
|
parts.push('-ss', fmtSeconds(spec.start));
|
|
92
92
|
if (spec.duration !== undefined)
|
|
93
93
|
parts.push('-t', fmtSeconds(spec.duration));
|
|
94
|
+
if (spec.maxFrames !== undefined)
|
|
95
|
+
parts.push('-frames:v', String(spec.maxFrames));
|
|
94
96
|
parts.push('-vf', 'fps=' + (spec.fps ?? 1), spec.output);
|
|
95
97
|
return parts;
|
|
96
98
|
}
|
|
99
|
+
/** 定点抽帧:在指定时间点取一帧。 */
|
|
100
|
+
export function frameAtArgs(ffmpeg, spec) {
|
|
101
|
+
const flag = overwriteFlag(spec.overwrite);
|
|
102
|
+
return [ffmpeg, flag, '-ss', fmtSeconds(spec.time), '-i', spec.input, '-frames:v', '1', spec.output];
|
|
103
|
+
}
|
|
97
104
|
/** GIF 第一遍:调色板生成(palettegen)。 */
|
|
98
105
|
export function gifPaletteArgs(ffmpeg, spec) {
|
|
99
106
|
const filter = 'fps=' + spec.fps + ',scale=' + spec.width + ':-1:flags=lanczos,palettegen';
|
package/lib/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { type FfmpegConfig } from './config.js';
|
|
|
12
12
|
import { type SubprocessSpawnLike } from './exec.js';
|
|
13
13
|
import { type FfmpegToolDefinition } from './tools.js';
|
|
14
14
|
/** cordis 服务注入:apply 里要用 ctx.subprocess 与 ctx.tools,必须显式声明,否则宿主会抛 cannot get property without inject。 */
|
|
15
|
+
export declare const name = "ffmpeg";
|
|
15
16
|
export declare const inject: string[];
|
|
16
17
|
/** 插件所需的最小 ctx 面(社区插件不依赖宿主内部类型)。 */
|
|
17
18
|
export interface FfmpegPluginContext {
|
package/lib/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { resolveConfig } from './config.js';
|
|
|
12
12
|
import { createSubprocessRunner } from './exec.js';
|
|
13
13
|
import { buildFfmpegTools } from './tools.js';
|
|
14
14
|
/** cordis 服务注入:apply 里要用 ctx.subprocess 与 ctx.tools,必须显式声明,否则宿主会抛 cannot get property without inject。 */
|
|
15
|
+
export const name = 'ffmpeg';
|
|
15
16
|
export const inject = ['subprocess', 'tools'];
|
|
16
17
|
/**
|
|
17
18
|
* 插件入口:解析配置、封装 subprocess 执行器并注册七个视频工具。
|
package/lib/tools.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 八个面向模型的视频工具:probe / cut / concat / encode / subtitle / extract / gif / frames。
|
|
3
3
|
* 直接调用 ctx.tools.register 注册【编译好的 JSON Schema】参数与 canonical 输出。
|
|
4
4
|
*
|
|
5
5
|
* @module dsh-ffmpeg/tools
|
|
6
6
|
*/
|
|
7
7
|
import { type ResolvedFfmpegConfig } from './config.js';
|
|
8
8
|
import { type ProcessRunner } from './exec.js';
|
|
9
|
+
import { type MediaInfo } from './ffprobe.js';
|
|
9
10
|
/** 模型可见的内容块。 */
|
|
10
11
|
export interface ContentBlock {
|
|
11
12
|
type: 'text';
|
|
@@ -27,6 +28,8 @@ export interface FfmpegToolDefinition {
|
|
|
27
28
|
execute(args: unknown, exec: unknown): Promise<unknown>;
|
|
28
29
|
timeoutMs?: number;
|
|
29
30
|
}
|
|
31
|
+
/** 生成一行人类可读的媒体摘要:容器、时长、主视频、帧率、码率、体积。 */
|
|
32
|
+
export declare function buildProbeSummary(media: MediaInfo): string;
|
|
30
33
|
/**
|
|
31
34
|
* 构建七个工具定义。
|
|
32
35
|
* @param config - 已解析配置。
|
package/lib/tools.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 八个面向模型的视频工具:probe / cut / concat / encode / subtitle / extract / gif / frames。
|
|
3
3
|
* 直接调用 ctx.tools.register 注册【编译好的 JSON Schema】参数与 canonical 输出。
|
|
4
4
|
*
|
|
5
5
|
* @module dsh-ffmpeg/tools
|
|
6
6
|
*/
|
|
7
|
-
import { rmSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { mkdirSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
8
8
|
import { randomUUID } from 'node:crypto';
|
|
9
9
|
import { tmpdir } from 'node:os';
|
|
10
10
|
import { basename, dirname, extname, join } from 'node:path';
|
|
11
|
-
import { concatArgs, concatListContent, cutArgs, ENCODE_PRESETS, encodeArgs, extractArgs, fmtSeconds, gifPaletteArgs, gifUseArgs, probeArgs, subtitleArgs, } from './args.js';
|
|
11
|
+
import { concatArgs, concatListContent, cutArgs, ENCODE_PRESETS, encodeArgs, extractArgs, fmtSeconds, frameAtArgs, gifPaletteArgs, gifUseArgs, probeArgs, subtitleArgs, } from './args.js';
|
|
12
12
|
import { parseProbeJson } from './ffprobe.js';
|
|
13
13
|
import { assertInputFile, resolveOutputPath, sanitizeName } from './paths.js';
|
|
14
14
|
import { parseTimeArg } from './config.js';
|
|
@@ -98,6 +98,7 @@ const probeSchema = {
|
|
|
98
98
|
properties: {
|
|
99
99
|
ok: { type: 'boolean' },
|
|
100
100
|
input: { type: 'string' },
|
|
101
|
+
summary: { type: 'string' },
|
|
101
102
|
formatName: { type: 'string' },
|
|
102
103
|
durationSeconds: { type: 'number' },
|
|
103
104
|
sizeBytes: { type: 'number' },
|
|
@@ -114,6 +115,36 @@ const produceSchema = {
|
|
|
114
115
|
properties: { output: { type: 'string' } },
|
|
115
116
|
additionalProperties: true,
|
|
116
117
|
};
|
|
118
|
+
// ---------- probe 摘要 ----------
|
|
119
|
+
/** 码率人类可读。 */
|
|
120
|
+
function formatBitrate(bps) {
|
|
121
|
+
if (bps === null)
|
|
122
|
+
return '码率未知';
|
|
123
|
+
if (bps >= 1000000)
|
|
124
|
+
return (bps / 1000000).toFixed(2) + ' Mbps';
|
|
125
|
+
return Math.round(bps / 1000) + ' kbps';
|
|
126
|
+
}
|
|
127
|
+
/** 生成一行人类可读的媒体摘要:容器、时长、主视频、帧率、码率、体积。 */
|
|
128
|
+
export function buildProbeSummary(media) {
|
|
129
|
+
const parts = [];
|
|
130
|
+
if (media.formatName !== '')
|
|
131
|
+
parts.push(media.formatName);
|
|
132
|
+
if (media.durationSeconds !== null)
|
|
133
|
+
parts.push(fmtSeconds(media.durationSeconds));
|
|
134
|
+
if (media.video !== null) {
|
|
135
|
+
const v = media.video;
|
|
136
|
+
parts.push((v.codec !== '' ? v.codec + ' ' : '') + v.width + 'x' + v.height);
|
|
137
|
+
if (v.fps !== null)
|
|
138
|
+
parts.push(Math.round(v.fps * 100) / 100 + ' fps');
|
|
139
|
+
}
|
|
140
|
+
parts.push(formatBitrate(media.bitrate));
|
|
141
|
+
if (media.sizeBytes !== null) {
|
|
142
|
+
const mb = media.sizeBytes / 1024 / 1024;
|
|
143
|
+
parts.push(mb >= 1 ? mb.toFixed(1) + ' MB' : Math.round(media.sizeBytes / 1024) + ' KB');
|
|
144
|
+
}
|
|
145
|
+
parts.push('音频流 ' + media.audio.length + ' / 字幕流 ' + media.subtitles.length);
|
|
146
|
+
return parts.join(',');
|
|
147
|
+
}
|
|
117
148
|
// ---------- 工具构建 ----------
|
|
118
149
|
/**
|
|
119
150
|
* 构建七个工具定义。
|
|
@@ -136,6 +167,8 @@ export function buildFfmpegTools(config, runner) {
|
|
|
136
167
|
const videos = Array.isArray(rec.videos) ? rec.videos : [];
|
|
137
168
|
const video = videos.length > 0 ? asRecord(videos[0]) : asRecord(rec.video);
|
|
138
169
|
const lines = ['媒体信息(' + rec.input + '):'];
|
|
170
|
+
if (typeof rec.summary === 'string' && rec.summary !== '')
|
|
171
|
+
lines.push('- 摘要:' + rec.summary);
|
|
139
172
|
lines.push('- 容器:' + rec.formatName + ',时长:' + (rec.durationSeconds ?? '未知') + ' 秒,大小:' + (rec.sizeBytes ?? '未知') + ' 字节');
|
|
140
173
|
if (videos.length > 0) {
|
|
141
174
|
lines.push('- 视频流 ' + videos.length + ' 个;主视频:' + video.codec + ' ' + video.width + 'x' + video.height + ',帧率:' + (video.fps ?? '未知'));
|
|
@@ -149,7 +182,7 @@ export function buildFfmpegTools(config, runner) {
|
|
|
149
182
|
const input = assertInputFile(requiredString(args, 'input', '输入文件'));
|
|
150
183
|
const result = await runChecked(runner, probeArgs(cfg.ffprobePath, input), Math.min(timeout, 60000), 'ffprobe');
|
|
151
184
|
const media = parseProbeJson(result.stdout);
|
|
152
|
-
return { ok: true, input, ...media };
|
|
185
|
+
return { ok: true, input, summary: buildProbeSummary(media), ...media };
|
|
153
186
|
},
|
|
154
187
|
timeoutMs: Math.min(timeout, 60000),
|
|
155
188
|
};
|
|
@@ -418,5 +451,102 @@ export function buildFfmpegTools(config, runner) {
|
|
|
418
451
|
},
|
|
419
452
|
timeoutMs: timeout,
|
|
420
453
|
};
|
|
421
|
-
|
|
454
|
+
const frames = {
|
|
455
|
+
name: 'ffmpeg_frames',
|
|
456
|
+
description: '从视频批量抽帧为图片(PNG/JPG)。两种模式:every(固定秒间隔抽帧,如 every=2 表示每 2 秒一帧,默认 1)或 times(指定时间点列表,如 ["00:00:05","00:01:30"],最多 20 个)。maxFrames 限制 every 模式的帧数上限(1-500,默认 100)。返回输出目录、文件清单与数量,便于后续视觉模型读图。',
|
|
457
|
+
parameters: compileParameters({
|
|
458
|
+
input: { type: 'string', required: true, description: '输入视频(必填)。' },
|
|
459
|
+
every: { type: 'number', description: '抽帧间隔秒数(与 times 二选一,默认 1)。' },
|
|
460
|
+
times: { type: 'array', items: { type: 'string' }, description: '时间点列表(与 every 二选一,最多 20 个,秒数或 HH:MM:SS.mmm)。' },
|
|
461
|
+
maxFrames: { type: 'integer', description: 'every 模式帧数上限 1-500(默认 100)。' },
|
|
462
|
+
outputDir: { type: 'string', description: '输出目录(可选,默认输入同目录 <文件名>-frames)。' },
|
|
463
|
+
format: { type: 'string', description: '图片格式:png(默认)或 jpg。' },
|
|
464
|
+
}),
|
|
465
|
+
output: {
|
|
466
|
+
schema: baseSchema,
|
|
467
|
+
render: buildTextRenderer((_args, value) => {
|
|
468
|
+
const rec = asRecord(value);
|
|
469
|
+
return ['抽帧完成:共 ' + rec.count + ' 张,输出目录 ' + rec.outputDir + (rec.mode === 'times' ? '(指定时间点)' : '(每 ' + rec.every + ' 秒一帧)')];
|
|
470
|
+
}),
|
|
471
|
+
},
|
|
472
|
+
async execute(rawArgs) {
|
|
473
|
+
const args = asRecord(rawArgs);
|
|
474
|
+
const input = assertInputFile(requiredString(args, 'input', '输入文件'));
|
|
475
|
+
const formatRaw = optionalString(args, 'format')?.toLowerCase() ?? 'png';
|
|
476
|
+
if (formatRaw !== 'png' && formatRaw !== 'jpg' && formatRaw !== 'jpeg')
|
|
477
|
+
throw new Error('format 只支持 png 或 jpg。');
|
|
478
|
+
const ext = formatRaw === 'png' ? '.png' : '.jpg';
|
|
479
|
+
const times = stringArray(args, 'times');
|
|
480
|
+
const outDir = optionalString(args, 'outputDir') ?? join(dirname(input), sanitizeName(basename(input, extname(input))) + '-frames');
|
|
481
|
+
mkdirSync(outDir, { recursive: true });
|
|
482
|
+
if (times.length > 0) {
|
|
483
|
+
if (times.length > 20)
|
|
484
|
+
throw new Error('times 最多 20 个时间点(当前 ' + times.length + ' 个)。');
|
|
485
|
+
let i = 0;
|
|
486
|
+
for (const raw of times) {
|
|
487
|
+
const at = parseTimeArg(raw);
|
|
488
|
+
if (at === null)
|
|
489
|
+
throw new Error('times 里第 ' + (i + 1) + ' 个时间点非法:' + raw + '(请用秒数或 HH:MM:SS.mmm)。');
|
|
490
|
+
const target = join(outDir, 'frame-' + String(i + 1).padStart(3, '0') + ext);
|
|
491
|
+
await runChecked(runner, frameAtArgs(cfg.ffmpegPath, { input, time: at, output: target, overwrite: cfg.overwrite }), timeout, 'ffmpeg 定点抽帧');
|
|
492
|
+
i++;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
const every = optionalNumber(args, 'every') ?? 1;
|
|
497
|
+
if (every <= 0)
|
|
498
|
+
throw new Error('every 必须大于 0。');
|
|
499
|
+
const maxFramesRaw = args.maxFrames;
|
|
500
|
+
const maxFrames = typeof maxFramesRaw === 'number' && Number.isInteger(maxFramesRaw) ? Math.min(500, Math.max(1, maxFramesRaw)) : 100;
|
|
501
|
+
const pattern = join(outDir, 'frame-%03d' + ext);
|
|
502
|
+
await runChecked(runner, extractArgs(cfg.ffmpegPath, { input, what: 'frames', output: pattern, overwrite: cfg.overwrite, fps: 1 / every, streamIndex: 0, maxFrames }), timeout, 'ffmpeg 抽帧');
|
|
503
|
+
}
|
|
504
|
+
const files = readdirSync(outDir).filter((f) => f.startsWith('frame-') && f.endsWith(ext)).sort();
|
|
505
|
+
const every = optionalNumber(args, 'every') ?? 1;
|
|
506
|
+
return { outputDir: outDir, mode: times.length > 0 ? 'times' : 'every', every: times.length > 0 ? null : every, count: files.length, files: files.slice(0, 200) };
|
|
507
|
+
},
|
|
508
|
+
timeoutMs: timeout,
|
|
509
|
+
};
|
|
510
|
+
const health = {
|
|
511
|
+
name: 'ffmpeg_health',
|
|
512
|
+
description: 'dsh-ffmpeg 自检:验证 ffmpeg / ffprobe 可执行文件是否可用(执行 -version)。遇到问题时先运行本工具定位。',
|
|
513
|
+
parameters: compileParameters({}),
|
|
514
|
+
output: {
|
|
515
|
+
schema: baseSchema,
|
|
516
|
+
render: buildTextRenderer((_args, value) => {
|
|
517
|
+
const rec = asRecord(value);
|
|
518
|
+
const checks = Array.isArray(rec.checks) ? rec.checks : [];
|
|
519
|
+
const lines = ['dsh-ffmpeg 自检' + (rec.ok === true ? ':正常。' : ':发现问题。')];
|
|
520
|
+
for (const item of checks) {
|
|
521
|
+
const c = asRecord(item);
|
|
522
|
+
lines.push('- ' + c.name + ':' + (c.ok === true ? '✅ ' + String(c.version ?? '') : '❌ ' + String(c.detail ?? '')));
|
|
523
|
+
}
|
|
524
|
+
return lines;
|
|
525
|
+
}),
|
|
526
|
+
},
|
|
527
|
+
async execute() {
|
|
528
|
+
const checks = [];
|
|
529
|
+
let ok = true;
|
|
530
|
+
for (const [label, bin] of [['ffmpeg', cfg.ffmpegPath], ['ffprobe', cfg.ffprobePath]]) {
|
|
531
|
+
try {
|
|
532
|
+
const result = await runner.run([bin, '-version'], { timeoutMs: 15000 });
|
|
533
|
+
const firstLine = result.stdout.split(/\r?\n/)[0]?.trim() ?? '';
|
|
534
|
+
if (result.exitCode === 0) {
|
|
535
|
+
checks.push({ name: label, ok: true, path: bin, version: firstLine });
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
ok = false;
|
|
539
|
+
checks.push({ name: label, ok: false, path: bin, detail: '退出码 ' + String(result.exitCode) + ':' + firstLine });
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
catch (error) {
|
|
543
|
+
ok = false;
|
|
544
|
+
checks.push({ name: label, ok: false, path: bin, detail: error instanceof Error ? error.message : String(error) });
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return { ok, plugin: 'dsh-ffmpeg', checks };
|
|
548
|
+
},
|
|
549
|
+
timeoutMs: 30000,
|
|
550
|
+
};
|
|
551
|
+
return [probe, cut, concat, encode, subtitle, extract, gif, frames, health];
|
|
422
552
|
}
|
package/package.json
CHANGED
|
@@ -1,61 +1,60 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-ffmpeg",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "DSH 视频处理工具插件:ffmpeg_probe/cut/concat/encode/subtitle/extract/gif 七工具,进程走官方 subprocess 服务,超时树级终止、环境变量路径回退与多视频流探测,零运行时依赖(ffmpeg 本体除外)。",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "lib/index.js",
|
|
7
|
-
"types": "lib/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./lib/index.d.ts",
|
|
11
|
-
"default": "./lib/index.js"
|
|
12
|
-
},
|
|
13
|
-
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
14
|
-
"./package.json": "./package.json"
|
|
15
|
-
},
|
|
16
|
-
"files": [
|
|
17
|
-
"lib",
|
|
18
|
-
"cordis.patch.yml",
|
|
19
|
-
"README.md",
|
|
20
|
-
"README.en.md"
|
|
21
|
-
],
|
|
22
|
-
"scripts": {
|
|
23
|
-
"build": "tsc -p tsconfig.json",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-ffmpeg",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "DSH 视频处理工具插件:ffmpeg_probe/cut/concat/encode/subtitle/extract/gif 七工具,进程走官方 subprocess 服务,超时树级终止、环境变量路径回退与多视频流探测,零运行时依赖(ffmpeg 本体除外)。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"lib",
|
|
18
|
+
"cordis.patch.yml",
|
|
19
|
+
"README.md",
|
|
20
|
+
"README.en.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -p tsconfig.json",
|
|
24
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
25
|
+
"test": "pnpm run build && node --test \"test/*.test.mjs\"",
|
|
26
|
+
"prepublishOnly": "pnpm run build"
|
|
27
|
+
},
|
|
28
|
+
"dsh": {
|
|
29
|
+
"bundle": {
|
|
30
|
+
"patch": "./cordis.patch.yml"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"dsh",
|
|
35
|
+
"deepseek-harness",
|
|
36
|
+
"plugin",
|
|
37
|
+
"ffmpeg",
|
|
38
|
+
"video",
|
|
39
|
+
"media",
|
|
40
|
+
"dsh-plugin"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=22"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^24.0.0",
|
|
48
|
+
"typescript": "^5.6.0"
|
|
49
|
+
},
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/STARDUSTLC666/dsh-ffmpeg.git"
|
|
53
|
+
},
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/STARDUSTLC666/dsh-ffmpeg/issues"
|
|
56
|
+
},
|
|
57
|
+
"homepage": "https://github.com/STARDUSTLC666/dsh-ffmpeg#readme",
|
|
58
|
+
"author": "stardustlc",
|
|
59
|
+
"packageManager": "pnpm@11.7.0"
|
|
60
|
+
}
|