@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.
@@ -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" })\`
@@ -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 | 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.1' }, { capabilities: { tools: {} } });
284
+ const server = new Server({ name: 'skills', version: '2.4.3' }, { 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.1",
3
+ "version": "2.4.3",
4
4
  "description": "MCP server that returns Python reference snippets and skill guides",
5
5
  "main": "index.js",
6
6
  "bin": {