@ww_nero/skills 2.4.1 → 2.4.3
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/assets/{pptx_to_pdf.py → pptx_to_png.py} +31 -17
- package/index.js +11 -10
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""使用 LibreOffice 无头模式将 PPT/PPTX
|
|
1
|
+
"""使用 LibreOffice 无头模式将 PPT/PPTX 转换为 PNG 图片。"""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
@@ -9,6 +9,9 @@ import subprocess
|
|
|
9
9
|
import sys
|
|
10
10
|
import tempfile
|
|
11
11
|
from pathlib import Path
|
|
12
|
+
from typing import List
|
|
13
|
+
|
|
14
|
+
from pdf2image import convert_from_path
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
def _detect_libreoffice() -> str:
|
|
@@ -27,7 +30,6 @@ def _detect_libreoffice() -> str:
|
|
|
27
30
|
if base:
|
|
28
31
|
candidates.append(str(Path(base) / 'LibreOffice' / 'program' / 'soffice.exe'))
|
|
29
32
|
|
|
30
|
-
# 通用命令放在末尾,优先尝试绝对路径
|
|
31
33
|
candidates.extend(['soffice', 'libreoffice'])
|
|
32
34
|
|
|
33
35
|
for candidate in candidates:
|
|
@@ -42,21 +44,22 @@ def _detect_libreoffice() -> str:
|
|
|
42
44
|
return ''
|
|
43
45
|
|
|
44
46
|
|
|
45
|
-
def
|
|
47
|
+
def pptx_to_png(input_file: str | Path, output_dir: str | Path, dpi: int = 300) -> List[Path]:
|
|
48
|
+
"""将 PPTX 转换为 PNG 图片,返回生成的文件路径列表。"""
|
|
46
49
|
pptx_path = Path(input_file).expanduser().resolve()
|
|
47
50
|
if not pptx_path.exists():
|
|
48
51
|
raise FileNotFoundError(f'PPT/PPTX 文件不存在: {pptx_path}')
|
|
49
52
|
if pptx_path.is_dir():
|
|
50
53
|
raise ValueError(f'输入路径不是文件: {pptx_path}')
|
|
51
54
|
|
|
52
|
-
output_path = Path(
|
|
53
|
-
output_path.
|
|
55
|
+
output_path = Path(output_dir).expanduser().resolve()
|
|
56
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
54
57
|
|
|
55
58
|
libreoffice_cmd = _detect_libreoffice()
|
|
56
59
|
if not libreoffice_cmd:
|
|
57
60
|
raise RuntimeError('未找到 LibreOffice,请先安装(例如 apt install libreoffice 或从官网安装包)。')
|
|
58
61
|
|
|
59
|
-
with tempfile.TemporaryDirectory(prefix='
|
|
62
|
+
with tempfile.TemporaryDirectory(prefix='pptx_to_png_') as temp_dir:
|
|
60
63
|
cmd = [
|
|
61
64
|
libreoffice_cmd,
|
|
62
65
|
'--headless',
|
|
@@ -71,33 +74,44 @@ def pptx_to_pdf(input_file: str | Path, output_file: str | Path) -> Path:
|
|
|
71
74
|
message = result.stderr.strip() or result.stdout.strip() or 'LibreOffice 返回非零退出码'
|
|
72
75
|
raise RuntimeError(f'LibreOffice 转换失败: {message}')
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
if not
|
|
77
|
+
generated_pdf = Path(temp_dir) / f'{pptx_path.stem}.pdf'
|
|
78
|
+
if not generated_pdf.exists():
|
|
76
79
|
pdf_candidates = list(Path(temp_dir).glob('*.pdf'))
|
|
77
80
|
if len(pdf_candidates) == 1:
|
|
78
|
-
|
|
81
|
+
generated_pdf = pdf_candidates[0]
|
|
79
82
|
else:
|
|
80
83
|
raise RuntimeError('未找到 LibreOffice 生成的 PDF 文件')
|
|
81
84
|
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
images = convert_from_path(str(generated_pdf), dpi=dpi, fmt='png')
|
|
86
|
+
|
|
87
|
+
saved_files: List[Path] = []
|
|
88
|
+
for index, image in enumerate(images, start=1):
|
|
89
|
+
file_name = f'page_{index:04d}.png'
|
|
90
|
+
target = output_path / file_name
|
|
91
|
+
image.save(str(target), 'PNG')
|
|
92
|
+
saved_files.append(target)
|
|
84
93
|
|
|
85
|
-
|
|
86
|
-
return output_path
|
|
94
|
+
return saved_files
|
|
87
95
|
|
|
88
96
|
|
|
89
97
|
def _parse_args() -> argparse.Namespace:
|
|
90
|
-
parser = argparse.ArgumentParser(description='将 PPT/PPTX 转换为
|
|
98
|
+
parser = argparse.ArgumentParser(description='将 PPT/PPTX 转换为 PNG 图片(调用 LibreOffice + pdf2image)。')
|
|
91
99
|
parser.add_argument('--input', required=True, help='PPT/PPTX 文件路径')
|
|
92
|
-
parser.add_argument('--output', required=True, help='
|
|
100
|
+
parser.add_argument('--output-dir', required=True, help='保存图片的目录')
|
|
101
|
+
parser.add_argument('--dpi', type=int, default=300, help='输出图片的 DPI,默认 300')
|
|
93
102
|
return parser.parse_args()
|
|
94
103
|
|
|
95
104
|
|
|
96
105
|
def main() -> None:
|
|
97
106
|
args = _parse_args()
|
|
98
107
|
try:
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
results = pptx_to_png(args.input, args.output_dir, dpi=args.dpi)
|
|
109
|
+
if not results:
|
|
110
|
+
print('未生成任何图片')
|
|
111
|
+
return
|
|
112
|
+
print(f'成功生成 {len(results)} 张图片:')
|
|
113
|
+
for path in results:
|
|
114
|
+
print(path)
|
|
101
115
|
except Exception as error:
|
|
102
116
|
print(f'转换失败: {error}', file=sys.stderr)
|
|
103
117
|
sys.exit(1)
|
package/index.js
CHANGED
|
@@ -96,14 +96,15 @@ const SNIPPETS = {
|
|
|
96
96
|
'如需大批量转换,可将 convert_from_path 的线程参数打开以提升性能。'
|
|
97
97
|
]
|
|
98
98
|
},
|
|
99
|
-
|
|
100
|
-
title: 'PPTX 转
|
|
101
|
-
description: '使用 LibreOffice
|
|
102
|
-
codeFile: '
|
|
99
|
+
pptx_to_png: {
|
|
100
|
+
title: 'PPTX 转 PNG',
|
|
101
|
+
description: '使用 LibreOffice 无头模式转换为 PDF,再通过 pdf2image 转成 PNG 图片,兼容 Windows/macOS/Linux。',
|
|
102
|
+
codeFile: 'pptx_to_png.py',
|
|
103
103
|
notes: [
|
|
104
104
|
'脚本会自动探测 soffice 路径,如未安装需先安装 LibreOffice。',
|
|
105
|
-
'
|
|
106
|
-
'
|
|
105
|
+
'内部先转 PDF 再转 PNG,中间文件在临时目录处理,不会残留。',
|
|
106
|
+
'支持自定义 dpi,默认 300dpi 用于打印级清晰度。',
|
|
107
|
+
'输出目录会自动创建,文件名形如 page_0001.png,便于批量处理。'
|
|
107
108
|
]
|
|
108
109
|
},
|
|
109
110
|
svg_to_pptx: {
|
|
@@ -156,7 +157,7 @@ const GUIDES = {
|
|
|
156
157
|
|---------|-------------|------|
|
|
157
158
|
| HTML → PNG | \`html_to_png\` | 使用 Playwright 渲染并截图 |
|
|
158
159
|
| PDF → PNG | \`pdf_to_png\` | 使用 pdf2image 分页转换 |
|
|
159
|
-
| PPTX →
|
|
160
|
+
| PPTX → PNG | \`pptx_to_png\` | 使用 LibreOffice + pdf2image |
|
|
160
161
|
| SVG → PPTX | \`svg_to_pptx\` | 转化为可编辑的 PPTX 元素 |
|
|
161
162
|
|
|
162
163
|
调用示例:使用 \`snippet\` 工具获取参考代码,如 \`snippet({ title: "pdf_to_png" })\`
|
|
@@ -181,7 +182,7 @@ const GUIDES = {
|
|
|
181
182
|
1. **规划PPT内容**,存储到markdown文件中:
|
|
182
183
|
- 每页Slide的规划需要包含:内容、布局方式、插图(只有用户明确提出要求时才添加插图)
|
|
183
184
|
- 不同页面应尽可能使用不同的布局方式,避免过于单调,可适当使用图表来呈现信息,例如折线图、柱状图、饼状图等;各正文页面背景和标题样式应保持一致
|
|
184
|
-
- 如果用户未明确提出要求,则页面默认比例为宽1280px、高720px
|
|
185
|
+
- 如果用户未明确提出要求,则页面默认比例为宽1280px、高720px,默认背景为深邃蓝色、弱渐变、商务简约风格
|
|
185
186
|
- 如果需要插入图片,则按照以下方式:
|
|
186
187
|
- 收集图片素材,优先使用用户提供的图片;如未提供,则使用图片生成工具生成合适的图片
|
|
187
188
|
- 将图片素材保存到\`images\`文件夹中,通过python脚本获取每个图片的宽高比例,然后对每张图片进行重命名,通过名称标识图片的内容及宽高比例,例如\`main_background_16_9.png\`表示该图片是主背景图,宽高比为16:9;对于用户提供的图片素材,可调用工具解读图片内容
|
|
@@ -255,7 +256,7 @@ const listTools = () => ({
|
|
|
255
256
|
},
|
|
256
257
|
title: {
|
|
257
258
|
type: 'string',
|
|
258
|
-
description: '示例类型,可选: html_to_png | pdf_to_png |
|
|
259
|
+
description: '示例类型,可选: html_to_png | pdf_to_png | pptx_to_png | svg_to_pptx',
|
|
259
260
|
enum: Object.keys(SNIPPETS)
|
|
260
261
|
}
|
|
261
262
|
},
|
|
@@ -280,7 +281,7 @@ const listTools = () => ({
|
|
|
280
281
|
]
|
|
281
282
|
});
|
|
282
283
|
|
|
283
|
-
const server = new Server({ name: 'skills', version: '2.4.
|
|
284
|
+
const server = new Server({ name: 'skills', version: '2.4.3' }, { capabilities: { tools: {} } });
|
|
284
285
|
|
|
285
286
|
server.setRequestHandler(ListToolsRequestSchema, async () => listTools());
|
|
286
287
|
|