@deepwhale-cn/dsh-pptx-batch 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +99 -2
- package/bin/cli.mjs +284 -0
- package/cordis.patch.yml +4 -0
- package/lib/api.js +27 -0
- package/lib/client.js +87 -0
- package/lib/core.mjs +52 -0
- package/lib/index.js +10 -0
- package/lib/pptx_tool.py +705 -0
- package/package.json +53 -4
- package/skill/pptx-batch.md +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 深鲸 DeepWhale
|
|
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.md
CHANGED
|
@@ -1,3 +1,100 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @deepwhale-cn/dsh-pptx-batch
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://dsh-plugin.org/plugins/feely0208/dsh-pptx-batch)
|
|
4
|
+
|
|
5
|
+
**整目录的 PPT 一次处理完**:批量填模板占位符、按形状名换图片、提取每页文本、看页数/形状数/图片数 —— 不打开 PowerPoint,不动原文件。
|
|
6
|
+
|
|
7
|
+
**关键词**:PPT 批量填模板 · pptx 批量替换 · PPT 批量换 Logo · 提取 PPT 文本 · PPT 页数统计 · 汇报批量生成 · 模板占位符替换 · 命令行处理 PPT
|
|
8
|
+
|
|
9
|
+
## 不装会怎样
|
|
10
|
+
|
|
11
|
+
30 份给不同客户的报价 PPT,模板里写着 `{{客户}}`、`{{金额}}`:不装就得一份份打开、双击文本框、输入、保存、关掉,
|
|
12
|
+
一小时起步,改到第 20 份时手一滑把上一位客户的名字留在下一份里,也没人发现。
|
|
13
|
+
|
|
14
|
+
五十份材料的 Logo 要换新版:人工做法是逐份删图、插图、再对齐一遍位置,版面还容易歪。
|
|
15
|
+
|
|
16
|
+
要核对一批 PPT 每页写了什么、各有多少页多少张图:只能一份份翻,翻完还记不住。
|
|
17
|
+
|
|
18
|
+
这些都属于「机械但量大、还容易漏」的活。更麻烦的是直接让 AI 改原文件,改坏了没有退路 ——
|
|
19
|
+
这个插件**只读输入**,产物写到单独的输出目录,同名文件自动加序号,不覆盖任何东西。
|
|
20
|
+
|
|
21
|
+
## 能做什么
|
|
22
|
+
|
|
23
|
+
| 子命令 | 作用 | 产物(可核对) |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `fill` | 按 `--set key=value` 批量填幻灯片里的 `{{key}}` 占位符(文本框、占位符、表格单元格、组合形状) | 填好的 `.pptx` 副本;JSON 里给出每份替换了几处、哪个 key 几处 |
|
|
26
|
+
| `replace-image` | 把名为 `--shape` 的图片形状换成 `--image` 指定的图片 | 换图后的 `.pptx` 副本;图片数量不变、位置尺寸不变、形状名不变 |
|
|
27
|
+
| `extract-text` | 提取每页文本:标题 + 正文 + 表格 + 备注 | 每份 PPT 一个同名 `.txt`,按「第 N 页」分块 |
|
|
28
|
+
| `info` | 输出每份 PPT 的页数、形状数、图片数 | 终端表格;`--json` 给脚本用 |
|
|
29
|
+
|
|
30
|
+
共同约定:
|
|
31
|
+
|
|
32
|
+
- **不修改输入文件**,默认输出到 `<输入目录>/_输出`,可用 `--out` 指定;
|
|
33
|
+
- **同名文件不覆盖**:已存在时自动变成 `简报_1.pptx`、`简报_2.pptx`;
|
|
34
|
+
- 目录递归处理,自动跳过 PowerPoint 的 `~$` 临时文件和 `_输出` 目录;
|
|
35
|
+
- `--json` 输出结构化结果,方便脚本 / CI 断言;出错时退出码非 0 并说明缺什么;
|
|
36
|
+
- `fill` 填完会报出**还剩哪些 `{{...}}` 没给值**,模板里拼错 key 一眼能看出来。
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# 批量填模板:一份模板 + 一个客户一套值
|
|
40
|
+
dsh-pptx-batch fill --input ./模板 --out ./已填 \
|
|
41
|
+
--set 客户=客户甲 --set 金额=128000 --set 日期=2026-10-24
|
|
42
|
+
|
|
43
|
+
# 批量换 Logo(形状名要和 PowerPoint「选择窗格」里显示的一致)
|
|
44
|
+
dsh-pptx-batch replace-image --input ./已填 --out ./换图 \
|
|
45
|
+
--shape Logo --image ./logo-新.png
|
|
46
|
+
|
|
47
|
+
# 提取每页文本
|
|
48
|
+
dsh-pptx-batch extract-text --input ./模板 --out ./文本
|
|
49
|
+
|
|
50
|
+
# 看一眼结构
|
|
51
|
+
dsh-pptx-batch info --input ./模板 --json
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`fill` 保留格式的方式:占位符整段落在一个 run 里时,这个 run 的字体、字号、颜色完全不动;
|
|
55
|
+
只有跨多个 run 的占位符(同一段字被 PowerPoint 拆成几块)才会把这几段并到起始 run 的格式上。
|
|
56
|
+
|
|
57
|
+
## 不能做什么
|
|
58
|
+
|
|
59
|
+
- ❌ 只支持 `.pptx`;`.ppt` 老格式、`.pptm`、加密/需要密码打开的文档不支持 —— `.ppt` 请先用 PowerPoint/WPS 另存为 `.pptx`。
|
|
60
|
+
- ❌ `fill` **不处理**图表文字、SmartArt 文字、图片里的文字、批注、母版/版式上的文字,也不跨段落匹配。
|
|
61
|
+
- ❌ `fill` 是**字面匹配**(`{{key}}`,key 大小写敏感,允许 `{{ key }}` 这种空格),不支持正则、不支持一次给同名 key 多个值。
|
|
62
|
+
- ❌ **不保证排版 100% 不变**:跨 run 的占位符替换会合并该段文字的 run,段落内局部加粗/字号/颜色可能丢失;
|
|
63
|
+
重要模板请先跑一份对比再批量用。
|
|
64
|
+
- ❌ `replace-image` 只换图片内容:新图比例和原图不同时会被**拉伸**(裁剪设置沿用原来的);
|
|
65
|
+
只按形状名精确匹配,不支持按内容或页码挑选,同名形状会全部替换。
|
|
66
|
+
- ❌ `extract-text` 不提取图表数据、SmartArt、页码与母版文字;图片只留位置不留文字(没有 OCR)。
|
|
67
|
+
- ❌ `info` 的「形状数」是每页顶层形状数之和(组合形状算 1 个,其内部形状不计入)。
|
|
68
|
+
- ❌ 不是格式转换器:不导出 PDF、不做 OCR、不做批量重命名。
|
|
69
|
+
- ✅ 全部在本机由 python-pptx 处理,不联网、不上传文件。
|
|
70
|
+
|
|
71
|
+
## 安装
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
dsh plugin --profile web add @deepwhale-cn/dsh-pptx-batch
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
运行依赖本机的 **python3 + python-pptx**(插件不会替你装)。缺依赖时 CLI 会直接打印安装命令:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
python3 -m pip install python-pptx
|
|
81
|
+
# macOS 没有 python3 时
|
|
82
|
+
brew install python3
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 用法
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
dsh-pptx-batch --help
|
|
89
|
+
dsh-pptx-batch --demo # 自造样例 PPT 跑完整流程,打印产物路径
|
|
90
|
+
dsh-pptx-batch <action> --input <文件或目录> [--out <目录>] [--json]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`--demo` 会在系统临时目录里造两份带占位符、表格和图片形状的样例 PPT,
|
|
94
|
+
依次跑 fill / replace-image / extract-text / info,不动你的任何文件,跑完直接看产物路径就知道它能干什么。
|
|
95
|
+
|
|
96
|
+
退出码:`0` 成功 · `1` 运行失败 · `2` 参数或输入问题 · `3` 缺依赖。
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT © 温州深鲸智能科技有限公司
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PPT 批量处理 CLI
|
|
4
|
+
*
|
|
5
|
+
* 形态:
|
|
6
|
+
* dsh-pptx-batch --help (无参数也打印 help,退出 0)
|
|
7
|
+
* dsh-pptx-batch --demo (自造样例跑完整流程,退出 0)
|
|
8
|
+
* dsh-pptx-batch <action> --input <文件或目录> [--out <目录>] [--json]
|
|
9
|
+
*
|
|
10
|
+
* 真实逻辑全部委托 python3 + python-pptx(lib/pptx_tool.py),
|
|
11
|
+
* Node 只负责参数校验、调用与人类可读输出。
|
|
12
|
+
*/
|
|
13
|
+
import { spawnSync } from 'node:child_process';
|
|
14
|
+
import { dirname, join } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
|
+
const PY_SCRIPT = join(PKG_ROOT, 'lib', 'pptx_tool.py');
|
|
19
|
+
const TITLE = 'PPT 批量处理';
|
|
20
|
+
const CMD = 'dsh-pptx-batch';
|
|
21
|
+
const ACTIONS = {
|
|
22
|
+
fill: '按 --set key=value 批量填模板里的 {{key}} 占位符',
|
|
23
|
+
'replace-image': '把名为 --shape 的图片形状换成 --image 指定的图片',
|
|
24
|
+
'extract-text': '提取每页文本(标题 + 正文 + 表格)到同名 .txt',
|
|
25
|
+
info: '输出每份 PPT 的页数 / 形状数 / 图片数',
|
|
26
|
+
};
|
|
27
|
+
const PY_HINT = [
|
|
28
|
+
'需要 python3 + python-pptx。安装:',
|
|
29
|
+
' python3 -m pip install python-pptx',
|
|
30
|
+
'没有 python3 时:',
|
|
31
|
+
' macOS: brew install python3',
|
|
32
|
+
' Ubuntu: sudo apt install python3',
|
|
33
|
+
].join('\n');
|
|
34
|
+
const LINE = '─'.repeat(56);
|
|
35
|
+
|
|
36
|
+
function help() {
|
|
37
|
+
console.log(`${TITLE}
|
|
38
|
+
|
|
39
|
+
用法:
|
|
40
|
+
${CMD} <action> --input <文件或目录> [--out <目录>] [--json]
|
|
41
|
+
${CMD} --demo
|
|
42
|
+
${CMD} --help
|
|
43
|
+
|
|
44
|
+
子命令:
|
|
45
|
+
${Object.entries(ACTIONS).map(([k, v]) => ` ${k.padEnd(14)}${v}`).join('\n')}
|
|
46
|
+
|
|
47
|
+
参数:
|
|
48
|
+
--input, -i .pptx 文件或目录(目录递归处理)
|
|
49
|
+
--out, -o 输出目录;默认 <输入目录>/_输出;同名文件不覆盖,自动加序号
|
|
50
|
+
--set key=value,可重复;对应幻灯片里的 {{key}} 占位符
|
|
51
|
+
--shape 要替换的图片形状名(精确匹配,大小写敏感)
|
|
52
|
+
--image 新图片文件(png / jpg / gif / bmp / tiff)
|
|
53
|
+
--json 输出 JSON(给脚本 / CI 用)
|
|
54
|
+
--demo 自造样例 PPT,跑完整流程并打印产物路径
|
|
55
|
+
|
|
56
|
+
示例:
|
|
57
|
+
${CMD} fill --input ./模板 --set 客户=客户甲 --set 金额=128000
|
|
58
|
+
${CMD} replace-image --input ./已填 --shape Logo --image ./logo-新.png
|
|
59
|
+
${CMD} extract-text --input ./模板 --out ./文本
|
|
60
|
+
${CMD} info --input ./模板 --json
|
|
61
|
+
|
|
62
|
+
依赖:python3 + python-pptx(缺失时会提示安装命令)`);
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function fail(message, hint = '', code = 2, json = false) {
|
|
67
|
+
if (json) {
|
|
68
|
+
console.log(JSON.stringify({ ok: false, code, message, hint }, null, 2));
|
|
69
|
+
} else {
|
|
70
|
+
console.error(`❌ ${message}`);
|
|
71
|
+
if (hint) console.error(`\n${hint}`);
|
|
72
|
+
}
|
|
73
|
+
process.exit(code);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function findPython() {
|
|
77
|
+
const candidates = [
|
|
78
|
+
process.env.DSH_PYTHON,
|
|
79
|
+
process.env.PPTX_BATCH_PYTHON,
|
|
80
|
+
'/opt/homebrew/bin/python3',
|
|
81
|
+
'python3',
|
|
82
|
+
'/usr/local/bin/python3',
|
|
83
|
+
'/usr/bin/python3',
|
|
84
|
+
].filter(Boolean);
|
|
85
|
+
for (const cmd of candidates) {
|
|
86
|
+
const r = spawnSync(cmd, ['-c', 'import sys; print(sys.version_info[0])'], {
|
|
87
|
+
encoding: 'utf8',
|
|
88
|
+
timeout: 10000,
|
|
89
|
+
});
|
|
90
|
+
if (!r.error && r.status === 0) return cmd;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function parseArgs(argv) {
|
|
96
|
+
const opt = {
|
|
97
|
+
action: null, input: null, out: null, set: [], shape: null, image: null,
|
|
98
|
+
json: false, demo: false,
|
|
99
|
+
};
|
|
100
|
+
const VALUE_FLAGS = new Set(['--input', '-i', '--out', '-o', '--set', '--shape', '--image']);
|
|
101
|
+
for (let i = 0; i < argv.length; i++) {
|
|
102
|
+
const raw = argv[i];
|
|
103
|
+
const eq = raw.indexOf('=');
|
|
104
|
+
const flag = eq > 0 ? raw.slice(0, eq) : raw;
|
|
105
|
+
const inline = eq > 0 ? raw.slice(eq + 1) : undefined;
|
|
106
|
+
if (flag === '--json') {
|
|
107
|
+
opt.json = true;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (flag === '--demo') {
|
|
111
|
+
opt.demo = true;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (flag === '--help' || flag === '-h') help();
|
|
115
|
+
if (VALUE_FLAGS.has(flag)) {
|
|
116
|
+
let value = inline;
|
|
117
|
+
if (value === undefined) {
|
|
118
|
+
value = argv[i + 1];
|
|
119
|
+
if (value === undefined || value.startsWith('--')) {
|
|
120
|
+
fail(`参数 ${flag} 缺少取值`, `例:${flag} <值>`);
|
|
121
|
+
}
|
|
122
|
+
i += 1;
|
|
123
|
+
}
|
|
124
|
+
if (flag === '--input' || flag === '-i') opt.input = value;
|
|
125
|
+
else if (flag === '--out' || flag === '-o') opt.out = value;
|
|
126
|
+
else if (flag === '--shape') opt.shape = value;
|
|
127
|
+
else if (flag === '--image') opt.image = value;
|
|
128
|
+
else opt.set.push(value);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (flag.startsWith('-')) {
|
|
132
|
+
fail(`未知参数:${raw}`, `用 ${CMD} --help 查看支持的参数。`);
|
|
133
|
+
}
|
|
134
|
+
if (!opt.action) {
|
|
135
|
+
opt.action = raw;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
fail(`多余的参数:${raw}`, '一次只能执行一个子命令。');
|
|
139
|
+
}
|
|
140
|
+
return opt;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function human(res) {
|
|
144
|
+
console.log('');
|
|
145
|
+
console.log(` ${TITLE} · ${res.action}`);
|
|
146
|
+
console.log(` ${LINE}`);
|
|
147
|
+
if (res.input) console.log(` 输入 ${res.input}`);
|
|
148
|
+
if (res.out) console.log(` 输出目录 ${res.out}`);
|
|
149
|
+
if (res.image) console.log(` 新图片 ${res.image}`);
|
|
150
|
+
console.log('');
|
|
151
|
+
for (const f of res.files || []) {
|
|
152
|
+
switch (res.action) {
|
|
153
|
+
case 'fill': {
|
|
154
|
+
const keys = Object.entries(f.keys || {})
|
|
155
|
+
.map(([k, v]) => `${k}×${v}`).join(' ');
|
|
156
|
+
console.log(` ✓ ${f.input_name} → ${f.output_name} · 替换 ${f.replacements} 处${keys ? `(${keys})` : ''}`);
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case 'replace-image':
|
|
160
|
+
if (f.replaced === 0) {
|
|
161
|
+
console.log(` – ${f.input_name} → ${f.output_name} · 跳过(没有该形状)`);
|
|
162
|
+
} else {
|
|
163
|
+
console.log(` ✓ ${f.input_name} → ${f.output_name} · 替换图片 ${f.replaced} 处`);
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
case 'extract-text':
|
|
167
|
+
console.log(` ✓ ${f.input_name} → ${f.output_name} · ${f.slides} 页 · ${f.chars} 字符`);
|
|
168
|
+
break;
|
|
169
|
+
case 'info':
|
|
170
|
+
console.log(` ✓ ${f.input_name} · ${f.pages} 页 · ${f.shapes} 个形状 · ${f.pictures} 张图片`);
|
|
171
|
+
break;
|
|
172
|
+
default:
|
|
173
|
+
console.log(` ✓ ${f.input_name} → ${f.output_name}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (res.unfilled && res.unfilled.length) {
|
|
177
|
+
console.log(`\n ⚠ 还有 ${res.unfilled.length} 个占位符没给值:${res.unfilled.join('、')}`);
|
|
178
|
+
console.log(' 确认 --set 的 key 和模板里的 {{key}} 完全一致(大小写、空格)。');
|
|
179
|
+
}
|
|
180
|
+
if (res.summary) console.log(`\n ${res.summary}`);
|
|
181
|
+
if (res.artifacts && res.artifacts.length) {
|
|
182
|
+
console.log('\n 产物:');
|
|
183
|
+
for (const a of res.artifacts) console.log(` ${a}`);
|
|
184
|
+
}
|
|
185
|
+
console.log('');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function humanDemo(res) {
|
|
189
|
+
console.log('');
|
|
190
|
+
console.log(` ${TITLE} · 演示(--demo)`);
|
|
191
|
+
console.log(` ${LINE}`);
|
|
192
|
+
console.log(` 演示目录 ${res.demoRoot}`);
|
|
193
|
+
console.log('');
|
|
194
|
+
for (const s of res.steps || []) {
|
|
195
|
+
console.log(` ✓ ${String(s.step).padEnd(6)} ${s.action.padEnd(14)} ${s.summary}`);
|
|
196
|
+
}
|
|
197
|
+
console.log('\n 产物:');
|
|
198
|
+
for (const a of res.artifacts || []) console.log(` ${a}`);
|
|
199
|
+
console.log('');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function callPython(py, action, payload) {
|
|
203
|
+
const r = spawnSync(py, [PY_SCRIPT, action, JSON.stringify(payload)], {
|
|
204
|
+
encoding: 'utf8',
|
|
205
|
+
maxBuffer: 128 * 1024 * 1024,
|
|
206
|
+
});
|
|
207
|
+
if (r.error) {
|
|
208
|
+
fail(`调用 python 失败:${r.error.message}`, PY_HINT, 3);
|
|
209
|
+
}
|
|
210
|
+
const lines = (r.stdout || '').trim().split('\n').filter(Boolean);
|
|
211
|
+
let result = null;
|
|
212
|
+
try {
|
|
213
|
+
result = JSON.parse(lines[lines.length - 1] || '');
|
|
214
|
+
} catch {
|
|
215
|
+
/* 下面统一报错 */
|
|
216
|
+
}
|
|
217
|
+
if (!result) {
|
|
218
|
+
const detail = (r.stderr || '').trim().split('\n').slice(-5).join('\n');
|
|
219
|
+
fail('Python 处理失败(没有拿到有效结果)', detail || PY_HINT, r.status || 1);
|
|
220
|
+
}
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const argv = process.argv.slice(2);
|
|
225
|
+
if (argv.length === 0) help();
|
|
226
|
+
if (argv.includes('--help') || argv.includes('-h')) help();
|
|
227
|
+
|
|
228
|
+
const opt = parseArgs(argv);
|
|
229
|
+
const python = findPython();
|
|
230
|
+
if (!python) {
|
|
231
|
+
fail('未找到 python3,无法处理 PPT 文件', PY_HINT, 3, opt.json);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (opt.demo) {
|
|
235
|
+
const res = callPython(python, 'demo', {});
|
|
236
|
+
if (!res.ok) fail(res.message, res.hint, res.code || 1, opt.json);
|
|
237
|
+
if (opt.json) console.log(JSON.stringify(res, null, 2));
|
|
238
|
+
else humanDemo(res);
|
|
239
|
+
process.exit(0);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!opt.action) {
|
|
243
|
+
fail('缺少子命令', `可用子命令:${Object.keys(ACTIONS).join(' / ')}\n用 ${CMD} --help 查看详情。`, 2, opt.json);
|
|
244
|
+
}
|
|
245
|
+
if (!Object.hasOwn(ACTIONS, opt.action)) {
|
|
246
|
+
fail(`未知子命令:${opt.action}`, `可用子命令:${Object.keys(ACTIONS).join(' / ')}`, 2, opt.json);
|
|
247
|
+
}
|
|
248
|
+
if (!opt.input) {
|
|
249
|
+
fail('缺少 --input(文件或目录)', `例:${CMD} ${opt.action} --input ./PPT目录`, 2, opt.json);
|
|
250
|
+
}
|
|
251
|
+
if (opt.action === 'fill') {
|
|
252
|
+
if (opt.set.length === 0) {
|
|
253
|
+
fail('缺少 --set(要填的占位符)',
|
|
254
|
+
`例:${CMD} fill --input ./模板 --set 客户=客户甲 --set 金额=128000`, 2, opt.json);
|
|
255
|
+
}
|
|
256
|
+
const bad = opt.set.find((s) => !s.includes('=') || !s.split('=')[0].trim());
|
|
257
|
+
if (bad !== undefined) {
|
|
258
|
+
fail(`--set 需要 key=value 形式:${bad}`, '例:--set 客户=客户甲', 2, opt.json);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (opt.action === 'replace-image') {
|
|
262
|
+
if (!opt.shape) {
|
|
263
|
+
fail('缺少 --shape(要替换的图片形状名)',
|
|
264
|
+
`例:${CMD} replace-image --input ./模板 --shape Logo --image ./logo.png`, 2, opt.json);
|
|
265
|
+
}
|
|
266
|
+
if (!opt.image) {
|
|
267
|
+
fail('缺少 --image(新图片文件)',
|
|
268
|
+
`例:${CMD} replace-image --input ./模板 --shape Logo --image ./logo.png`, 2, opt.json);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const payload = {
|
|
273
|
+
input: opt.input,
|
|
274
|
+
out: opt.out,
|
|
275
|
+
set: opt.set,
|
|
276
|
+
shape: opt.shape,
|
|
277
|
+
image: opt.image,
|
|
278
|
+
};
|
|
279
|
+
const res = callPython(python, opt.action, payload);
|
|
280
|
+
if (!res.ok) fail(res.message, res.hint, res.code || 1, opt.json);
|
|
281
|
+
|
|
282
|
+
if (opt.json) console.log(JSON.stringify(res, null, 2));
|
|
283
|
+
else human(res);
|
|
284
|
+
process.exit(0);
|
package/cordis.patch.yml
ADDED
package/lib/api.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** 深鲸 PPT 批量 · 宿主出口 */
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { run } from './core.mjs';
|
|
6
|
+
|
|
7
|
+
const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
|
+
const ROUTE = '/dsh-pptx-batch';
|
|
9
|
+
const VERSION = (() => { try { return JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf8')).version || ''; } catch { return ''; } })();
|
|
10
|
+
const 回 = (res, code, obj) => { const b = JSON.stringify(obj); res.writeHead(code, { 'content-type': 'application/json; charset=utf-8', 'content-length': Buffer.byteLength(b) }); res.end(b); };
|
|
11
|
+
const 读体 = async (req) => { const c = []; for await (const x of req) c.push(x); const t = Buffer.concat(c).toString('utf8'); if (!t) return {}; try { return JSON.parse(t); } catch { return {}; } };
|
|
12
|
+
|
|
13
|
+
export function registerApi(ctx) {
|
|
14
|
+
const handler = async (req, res) => {
|
|
15
|
+
try {
|
|
16
|
+
const u = new URL(req.url || '/', 'http://127.0.0.1');
|
|
17
|
+
if (u.pathname === `${ROUTE}/api/info`) return 回(res, 200, { ok: true, version: VERSION });
|
|
18
|
+
if (u.pathname === `${ROUTE}/api/run` && req.method === 'POST') {
|
|
19
|
+
const b = await 读体(req);
|
|
20
|
+
const r = await run(b);
|
|
21
|
+
return 回(res, 200, r);
|
|
22
|
+
}
|
|
23
|
+
return 回(res, 404, { ok: false, error: 'not found' });
|
|
24
|
+
} catch (e) { return 回(res, 500, { ok: false, text: e?.message || String(e) }); }
|
|
25
|
+
};
|
|
26
|
+
ctx.webServer.register({ kind: 'prefix', path: ROUTE, handler });
|
|
27
|
+
}
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 深鲸 PPT 批量 —— 面板(统一模板:表单 → /api/run → 文本结果)
|
|
3
|
+
*/
|
|
4
|
+
window.__ModuleLoader__.load({
|
|
5
|
+
id: '@deepwhale-cn/dsh-pptx-batch',
|
|
6
|
+
factory: (require) => {
|
|
7
|
+
var module = { exports: {} }; var exports = module.exports;
|
|
8
|
+
const react = require('react'); const h = react.createElement;
|
|
9
|
+
const SLOT_ICON = 'sidebar.panellist'; const SLOT_MAIN = 'main'; const PANEL_ID = 'pptx-batch';
|
|
10
|
+
const API = '/dsh-pptx-batch/api'; const BADGE = 'data:image/png;base64,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';
|
|
11
|
+
const OPS = [{"key": "extract-text", "label": "提取文字"}, {"key": "extract-images", "label": "导出图片"}, {"key": "list", "label": "统计页数"}];
|
|
12
|
+
const FIELDS = [{"k": "input", "label": "文件/目录", "ph": "例如 /Users/mac/提案"}, {"k": "out", "label": "图片输出目录", "ph": "导出图片时用;留空 = 输入目录/_图片"}];
|
|
13
|
+
const Ico = ({ size = 18 } = {}) => h('svg', { width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round' }, [
|
|
14
|
+
h('path', { key: 'a', d: 'M4 5h16v14H4z' }), h('path', { key: 'b', d: 'M8 9h8M8 13h8M8 17h5' }),
|
|
15
|
+
]);
|
|
16
|
+
const CSS = `
|
|
17
|
+
.gn-root{position:relative;display:flex;flex-direction:column;gap:14px;padding:18px 22px 52px;height:100%;overflow:auto}
|
|
18
|
+
.gn-head{display:flex;align-items:center;gap:10px}
|
|
19
|
+
.gn-title{font-size:16px;font-weight:600}
|
|
20
|
+
.gn-sub{color:var(--dsw-alias-text-secondary,#8b98a8);font-size:12px}
|
|
21
|
+
.gn-ver{margin-left:8px;font-size:11px;opacity:.75;border:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.12));border-radius:6px;padding:0 5px}
|
|
22
|
+
.gn-card{border:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.08));border-radius:12px;background:var(--dsw-alias-bg-layer-1,rgba(255,255,255,.03));padding:14px;display:flex;flex-direction:column;gap:10px}
|
|
23
|
+
.gn-row{display:flex;gap:8px;align-items:center}
|
|
24
|
+
.gn-label{font-size:12px;color:var(--dsw-alias-text-secondary,#8b98a8);width:92px;flex:none}
|
|
25
|
+
.gn-input,.gn-select{flex:1;min-width:0;padding:7px 9px;border-radius:8px;border:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.12));background:var(--dsw-alias-bg-base,transparent);color:inherit;font-size:13px;font-family:inherit}
|
|
26
|
+
.gn-btn{padding:7px 16px;border-radius:8px;border:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.14));background:var(--dsw-alias-brand-primary,#2f6f9f);color:#fff;font-size:13px;cursor:pointer}
|
|
27
|
+
.gn-btn[disabled]{opacity:.55;cursor:default}
|
|
28
|
+
.gn-out{white-space:pre-wrap;word-break:break-word;font-family:ui-monospace,Menlo,monospace;font-size:12px;line-height:1.65;max-height:420px;overflow:auto;background:var(--dsw-alias-bg-layer-2,rgba(0,0,0,.18));border-radius:8px;padding:10px}
|
|
29
|
+
.gn-ok{color:var(--dsw-alias-state-success-primary,#46b47a)}.gn-err{color:var(--dsw-alias-state-error-primary,#e0645a)}
|
|
30
|
+
.gn-tip{font-size:12px;color:var(--dsw-alias-text-secondary,#8b98a8)}
|
|
31
|
+
.gn-badge{position:absolute;right:16px;bottom:12px;display:inline-flex;opacity:.9}
|
|
32
|
+
.gn-badge img{width:121px;height:20px;display:block}
|
|
33
|
+
`;
|
|
34
|
+
function 面板() {
|
|
35
|
+
const [op, 设op] = react.useState(OPS[0] ? OPS[0].key : '');
|
|
36
|
+
const [values, 设values] = react.useState(() => Object.fromEntries(FIELDS.map((f) => [f.k, ''])));
|
|
37
|
+
const [版本, 设版本] = react.useState('');
|
|
38
|
+
const [跑, 设跑] = react.useState(false);
|
|
39
|
+
const [结果, 设结果] = react.useState(null);
|
|
40
|
+
react.useEffect(() => { void (async () => { try { const r = await fetch(`${API}/info`); const j = await r.json(); if (j.version) 设版本(String(j.version)); } catch {} })(); }, []);
|
|
41
|
+
const 执行 = async () => {
|
|
42
|
+
设跑(true); 设结果(null);
|
|
43
|
+
try {
|
|
44
|
+
const res = await fetch(`${API}/run`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ op, ...values }) });
|
|
45
|
+
设结果(await res.json());
|
|
46
|
+
} catch (e) { 设结果({ ok: false, text: String(e) }); } finally { 设跑(false); }
|
|
47
|
+
};
|
|
48
|
+
return h('div', { className: 'gn-root' }, [
|
|
49
|
+
h('style', { key: 'c' }, CSS),
|
|
50
|
+
h('div', { key: 'h', className: 'gn-head' }, [
|
|
51
|
+
h(Ico, { key: 'i' }),
|
|
52
|
+
h('div', { key: 't' }, [
|
|
53
|
+
h('div', { key: 'a', className: 'gn-title' }, '深鲸 PPT 批量'),
|
|
54
|
+
h('div', { key: 'b', className: 'gn-sub' }, ['提取文字 / 导出图片 / 看页数,一次处理整目录', 版本 ? h('span', { key: 'v', className: 'gn-ver' }, 'v' + 版本) : null]),
|
|
55
|
+
]),
|
|
56
|
+
]),
|
|
57
|
+
h('div', { key: 'f', className: 'gn-card' }, [
|
|
58
|
+
OPS.length > 1 ? h('div', { key: 'op', className: 'gn-row' }, [
|
|
59
|
+
h('span', { key: 'l', className: 'gn-label' }, '操作'),
|
|
60
|
+
h('select', { key: 's', className: 'gn-select', value: op, onChange: (e) => 设op(e.target.value) }, OPS.map((o) => h('option', { key: o.key, value: o.key }, o.label))),
|
|
61
|
+
]) : null,
|
|
62
|
+
...FIELDS.map((f) => h('div', { key: f.k, className: 'gn-row' }, [
|
|
63
|
+
h('span', { key: 'l', className: 'gn-label' }, f.label),
|
|
64
|
+
h('input', { key: 'i', className: 'gn-input', placeholder: f.ph || '', value: values[f.k], onChange: (e) => 设values({ ...values, [f.k]: e.target.value }) }),
|
|
65
|
+
])),
|
|
66
|
+
h('div', { key: 'a', className: 'gn-row', style: { justifyContent: 'flex-end' } }, [
|
|
67
|
+
h('button', { key: 'b', className: 'gn-btn', disabled: 跑, onClick: 执行 }, 跑 ? '处理中…' : '开始处理'),
|
|
68
|
+
]),
|
|
69
|
+
]),
|
|
70
|
+
结果 ? h('div', { key: 'r', className: 'gn-card' }, [
|
|
71
|
+
h('div', { key: 's', className: 结果.ok === false ? 'gn-err' : 'gn-ok' }, 结果.ok === false ? ('失败:' + (结果.text || '未知错误')) : '完成,结果见下方'),
|
|
72
|
+
结果.ok === false ? null : h('div', { key: 'o', className: 'gn-out' }, 结果.text || ''),
|
|
73
|
+
]) : null,
|
|
74
|
+
h('div', { key: 'tip', className: 'gn-tip' }, '需要 python3 + python-pptx(缺库会给安装提示)。只读 pptx,不修改原文件。'),
|
|
75
|
+
h('a', { key: 'bg', className: 'gn-badge', href: 'https://deepwhale.org.cn', target: '_blank', rel: 'noopener noreferrer', title: '深鲸桌面 · deepwhale.org.cn' },
|
|
76
|
+
h('img', { src: BADGE, width: 121, height: 20, alt: 'powered by dwh' })),
|
|
77
|
+
]);
|
|
78
|
+
}
|
|
79
|
+
const inject = ['slots'];
|
|
80
|
+
function apply(ctx) {
|
|
81
|
+
const 试 = (n, f) => { try { f(); } catch (e) { try { console.warn('[pptx-batch] 插槽注册失败:' + n, e && e.message); } catch {} } };
|
|
82
|
+
试('侧栏图标', () => ctx.slots.inject(SLOT_ICON, function* () { yield ctx.slots.register({ name: SLOT_ICON, id: PANEL_ID, order: 41, label: '深鲸 PPT 批量' }, Ico); }));
|
|
83
|
+
试('主面板', () => ctx.slots.inject(SLOT_MAIN, function* () { yield ctx.slots.register({ name: SLOT_MAIN, key: PANEL_ID }, 面板); }));
|
|
84
|
+
}
|
|
85
|
+
exports.inject = inject; exports.apply = apply; return module.exports;
|
|
86
|
+
},
|
|
87
|
+
});
|
package/lib/core.mjs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
const PY = String.raw`
|
|
3
|
+
import sys, json, os
|
|
4
|
+
op = sys.argv[1] if len(sys.argv) > 1 else 'extract-text'
|
|
5
|
+
inp = sys.argv[2] if len(sys.argv) > 2 else ''
|
|
6
|
+
out = sys.argv[3] if len(sys.argv) > 3 else ''
|
|
7
|
+
try:
|
|
8
|
+
from pptx import Presentation
|
|
9
|
+
except Exception:
|
|
10
|
+
print(json.dumps({"ok": False, "text": "需要 python3 + python-pptx:pip3 install python-pptx"}, ensure_ascii=False)); sys.exit(0)
|
|
11
|
+
|
|
12
|
+
def fail(msg):
|
|
13
|
+
print(json.dumps({"ok": False, "text": msg}, ensure_ascii=False)); sys.exit(0)
|
|
14
|
+
files = []
|
|
15
|
+
if os.path.isdir(inp):
|
|
16
|
+
for n in sorted(os.listdir(inp)):
|
|
17
|
+
if n.lower().endswith('.pptx') and not n.startswith('~$'): files.append(os.path.join(inp, n))
|
|
18
|
+
elif inp.lower().endswith('.pptx'):
|
|
19
|
+
files = [inp]
|
|
20
|
+
if not files:
|
|
21
|
+
print(json.dumps({"ok": False, "text": "没有找到 .pptx 文件"}, ensure_ascii=False)); sys.exit(0)
|
|
22
|
+
lines = []
|
|
23
|
+
try:
|
|
24
|
+
if op == 'list':
|
|
25
|
+
for f in files:
|
|
26
|
+
lines.append(os.path.basename(f) + ': ' + str(len(Presentation(f).slides)) + ' 页')
|
|
27
|
+
elif op == 'extract-images':
|
|
28
|
+
if not out: out = os.path.join(os.path.dirname(files[0]), '_图片')
|
|
29
|
+
os.makedirs(out, exist_ok=True); n = 0
|
|
30
|
+
for f in files:
|
|
31
|
+
for si, s in enumerate(Presentation(f).slides, 1):
|
|
32
|
+
for sh in s.shapes:
|
|
33
|
+
if sh.shape_type == 13:
|
|
34
|
+
n += 1
|
|
35
|
+
open(os.path.join(out, str(si) + '-' + str(n) + '.png'), 'wb').write(sh.image.blob)
|
|
36
|
+
lines.append('导出 ' + str(n) + ' 张图片到 ' + out)
|
|
37
|
+
else:
|
|
38
|
+
for f in files:
|
|
39
|
+
prs = Presentation(f); lines.append('## ' + os.path.basename(f))
|
|
40
|
+
for i, s in enumerate(prs.slides, 1):
|
|
41
|
+
texts = [sh.text_frame.text.strip() for sh in s.shapes if sh.has_text_frame and sh.text_frame.text.strip()]
|
|
42
|
+
lines.append('### 第 ' + str(i) + ' 页')
|
|
43
|
+
lines.extend(texts if texts else ['(无文字)'])
|
|
44
|
+
except Exception as e:
|
|
45
|
+
fail('无法读取 PPT(可能不是有效的 .pptx):' + str(e)[:200])
|
|
46
|
+
print(json.dumps({"ok": True, "text": chr(10).join(lines)}, ensure_ascii=False))
|
|
47
|
+
`;
|
|
48
|
+
export function run({ op = 'extract-text', input = '', out = '' } = {}) {
|
|
49
|
+
if (!input) return { ok: false, text: '请填写 PPT 文件或目录' };
|
|
50
|
+
const r = spawnSync('python3', ['-c', PY, op, input, out], { encoding: 'utf8' });
|
|
51
|
+
try { return JSON.parse((r.stdout || '').trim() || '{}'); } catch { return { ok: false, text: String((r.stderr || r.stdout || '执行失败')).slice(-400) }; }
|
|
52
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createPlugin, pkgRootFrom } from '@deepwhale-cn/dsh-guard-core/plugin';
|
|
2
|
+
|
|
3
|
+
export const { name, inject, apply } = createPlugin({
|
|
4
|
+
name: 'pptx-batch',
|
|
5
|
+
pkgRoot: pkgRootFrom(import.meta.url),
|
|
6
|
+
skillFile: 'skill/pptx-batch.md',
|
|
7
|
+
binFile: 'bin/cli.mjs',
|
|
8
|
+
description: 'PPT 批量填模板 / 替换文字与图片 / 导出内容清单(python-pptx)',
|
|
9
|
+
whenToUse: '当用户要批量处理整目录的 PowerPoint(.pptx)时使用:按 {{key}} 批量填模板(客户名、金额、日期)、按形状名批量换图片(Logo)、提取每页文本到 txt、统计页数/形状数/图片数。典型说法:「这套模板有 30 份要按客户发」「把每份 PPT 里的旧 Logo 换掉」「把这批 pptx 的文字导出来核对」。不用于 .ppt 老格式、加密文档、需要保持复杂动画与版式的场景。',
|
|
10
|
+
});
|