@ww_nero/skills 2.4.0 → 2.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.
@@ -1,4 +1,4 @@
1
- """使用 LibreOffice 无头模式将 PPT/PPTX 直接转换为 PDF。"""
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 pptx_to_pdf(input_file: str | Path, output_file: str | Path) -> Path:
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(output_file).expanduser().resolve()
53
- output_path.parent.mkdir(parents=True, exist_ok=True)
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='pptx_to_pdf_') as temp_dir:
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
- generated = Path(temp_dir) / f'{pptx_path.stem}.pdf'
75
- if not generated.exists():
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
- generated = pdf_candidates[0]
81
+ generated_pdf = pdf_candidates[0]
79
82
  else:
80
83
  raise RuntimeError('未找到 LibreOffice 生成的 PDF 文件')
81
84
 
82
- if output_path.exists():
83
- output_path.unlink()
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
- shutil.move(str(generated), str(output_path))
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 转换为 PDF(调用 LibreOffice 无头模式)。')
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='输出 PDF 文件路径')
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
- result = pptx_to_pdf(args.input, args.output)
100
- print(f'已生成 PDF: {result}')
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
- pptx_to_pdf: {
100
- title: 'PPTX 转 PDF',
101
- description: '使用 LibreOffice 无头模式批量转换,兼容 Windows/macOS/Linux。',
102
- codeFile: 'pptx_to_pdf.py',
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 → PDF | \`pptx_to_pdf\` | 使用 LibreOffice 无头模式 |
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" })\`
@@ -215,7 +216,7 @@ const buildSnippet = (title, workingDirectory) => {
215
216
  fs.writeFileSync(outputPath, code);
216
217
 
217
218
  const lines = [];
218
- lines.push(`文件名: ${entry.codeFile}`);
219
+ lines.push(`示例脚本已经存储到工作目录下,文件名: ${entry.codeFile},可直接作为工具模块导入使用。`);
219
220
  if (entry.notes && entry.notes.length > 0) {
220
221
  lines.push('');
221
222
  lines.push('注意事项:');
@@ -255,7 +256,7 @@ const listTools = () => ({
255
256
  },
256
257
  title: {
257
258
  type: 'string',
258
- description: '示例类型,可选: html_to_png | pdf_to_png | pptx_to_pdf | svg_to_pptx',
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.0' }, { capabilities: { tools: {} } });
284
+ const server = new Server({ name: 'skills', version: '2.4.2' }, { capabilities: { tools: {} } });
284
285
 
285
286
  server.setRequestHandler(ListToolsRequestSchema, async () => listTools());
286
287
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ww_nero/skills",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "description": "MCP server that returns Python reference snippets and skill guides",
5
5
  "main": "index.js",
6
6
  "bin": {