iflow-mcp_pingcy_app_chatppt 0.1.0__py3-none-any.whl

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.
ppt_utils.py ADDED
@@ -0,0 +1,266 @@
1
+ """
2
+ PPT 工具类 - 处理 PPT 到 PDF 和 PDF 到图片的转换。
3
+ 此工具类专注于文件格式转换功能,只使用 pypdfium2 进行图片转换。
4
+ """
5
+
6
+ import hashlib
7
+ import subprocess
8
+ import base64
9
+ from pathlib import Path
10
+ from typing import List
11
+ from io import BytesIO
12
+ import logging
13
+
14
+ # PDF 处理 - 只使用 pypdfium2
15
+ try:
16
+ import pypdfium2 as pdfium
17
+ PDFIUM_AVAILABLE = True
18
+ except ImportError:
19
+ PDFIUM_AVAILABLE = False
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class PPTUtils:
25
+ """PPT 文件操作的工具类。"""
26
+
27
+ def __init__(self, cache_dir: str = "./cache"):
28
+ """初始化PPT工具类,设置缓存目录。
29
+
30
+ Args:
31
+ cache_dir: 存储缓存文件的目录
32
+ """
33
+
34
+ self.cache_dir = Path(cache_dir)
35
+ self.cache_dir.mkdir(exist_ok=True)
36
+
37
+ # 为不同文件类型创建子目录
38
+ self.pdf_cache_dir = self.cache_dir / "pdf"
39
+ self.image_cache_dir = self.cache_dir / "images"
40
+ self.pdf_cache_dir.mkdir(exist_ok=True)
41
+ self.image_cache_dir.mkdir(exist_ok=True)
42
+
43
+ def _get_file_hash(self, file_path: str) -> str:
44
+ """计算文件的哈希值用于缓存。"""
45
+ with open(file_path, 'rb') as f:
46
+ return hashlib.md5(f.read()).hexdigest()
47
+
48
+ def ppt_to_pdf(self, ppt_path: str) -> str:
49
+ """将PPT文件转换为PDF。
50
+
51
+ Args:
52
+ ppt_path: PPT文件路径
53
+
54
+ Returns:
55
+ 生成的PDF文件路径
56
+
57
+ Raises:
58
+ FileNotFoundError: 如果PPT文件不存在
59
+ RuntimeError: 如果转换失败
60
+ """
61
+ ppt_path = Path(ppt_path)
62
+ if not ppt_path.exists():
63
+ raise FileNotFoundError(f"未找到PPT文件: {ppt_path}")
64
+
65
+ # 计算哈希值用于缓存
66
+ file_hash = self._get_file_hash(str(ppt_path))
67
+ pdf_filename = f"{ppt_path.stem}_{file_hash}.pdf"
68
+ pdf_path = self.pdf_cache_dir / pdf_filename
69
+
70
+ logger.info(f"转换PPT到PDF: {ppt_path} → {pdf_path}")
71
+ # 如果存在缓存的PDF则返回
72
+ if pdf_path.exists():
73
+ logger.info(f"使用缓存的PDF: {pdf_path}")
74
+ return str(pdf_path)
75
+
76
+ # 使用LibreOffice将PPT转换为PDF
77
+ try:
78
+ logger.info(f"正在将PPT转换为PDF: {ppt_path}")
79
+
80
+ # 使用LibreOffice将PPT转换为PDF
81
+ cmd = [
82
+ "/Applications/LibreOffice.app/Contents/MacOS/soffice",
83
+ "--headless",
84
+ "--convert-to", "pdf",
85
+ "--outdir", str(self.pdf_cache_dir),
86
+ str(ppt_path)
87
+ ]
88
+
89
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
90
+
91
+ if result.returncode != 0:
92
+ raise RuntimeError(f"LibreOffice转换失败: {result.stderr}")
93
+
94
+ # LibreOffice创建的PDF使用原始文件名
95
+ original_pdf = self.pdf_cache_dir / f"{ppt_path.stem}.pdf"
96
+
97
+ # 重命名以包含哈希值
98
+ if original_pdf.exists():
99
+ original_pdf.rename(pdf_path)
100
+ logger.info(f"PPT已转换为PDF: {pdf_path}")
101
+ return str(pdf_path)
102
+ else:
103
+ raise RuntimeError("LibreOffice未创建PDF文件")
104
+
105
+ except subprocess.TimeoutExpired:
106
+ raise RuntimeError("PPT转PDF转换超时")
107
+ except Exception as e:
108
+ raise RuntimeError(f"PPT转PDF转换失败: {str(e)}")
109
+
110
+ def pdf_to_images(self, pdf_path: str, dpi: int = 200) -> List[str]:
111
+ """使用pypdfium2将PDF转换为图片。
112
+
113
+ Args:
114
+ pdf_path: PDF文件路径
115
+ dpi: 图片转换分辨率(转换为scale参数)
116
+
117
+ Returns:
118
+ 生成的图片文件路径列表
119
+
120
+ Raises:
121
+ FileNotFoundError: 如果PDF文件不存在
122
+ RuntimeError: 如果转换失败或缺少pypdfium2
123
+ """
124
+ if not PDFIUM_AVAILABLE:
125
+ raise RuntimeError("pypdfium2 未安装。请运行: pip install pypdfium2")
126
+
127
+ pdf_path = Path(pdf_path)
128
+ if not pdf_path.exists():
129
+ raise FileNotFoundError(f"未找到PDF文件: {pdf_path}")
130
+
131
+ # 将DPI转换为scale参数
132
+ scale = dpi / 50.0
133
+
134
+ # 检查图片是否已存在
135
+ image_pattern = f"{pdf_path.stem}_page_*.jpg"
136
+ existing_images = list(self.image_cache_dir.glob(image_pattern))
137
+
138
+ if existing_images:
139
+ logger.info(f"使用缓存的图片: {len(existing_images)} 页")
140
+ return sorted([str(img) for img in existing_images])
141
+
142
+ try:
143
+ logger.info(f"正在使用pypdfium2将PDF转换为图片: {pdf_path}")
144
+
145
+ # 打开PDF文档
146
+ pdf = pdfium.PdfDocument(str(pdf_path))
147
+ image_paths = []
148
+
149
+ # 转换每一页
150
+ for i in range(len(pdf)):
151
+ page = pdf[i]
152
+ image = page.render(scale=scale).to_pil()
153
+
154
+ # 生成图片文件名
155
+ image_filename = f"{pdf_path.stem}_page_{i+1:03d}.jpg"
156
+ image_path = self.image_cache_dir / image_filename
157
+
158
+ # 保存图片
159
+ image.save(str(image_path), 'JPEG', quality=95)
160
+ image_paths.append(str(image_path))
161
+
162
+ logger.info(f"PDF已转换为 {len(image_paths)} 张图片")
163
+ return image_paths
164
+
165
+ except Exception as e:
166
+ raise RuntimeError(f"PDF转图片转换失败: {str(e)}")
167
+
168
+ def ppt_to_images(self, ppt_path: str, dpi: int = 200) -> List[str]:
169
+ """直接将PPT转换为图片 (PPT → PDF → 图片)。
170
+
171
+ Args:
172
+ ppt_path: PPT文件路径
173
+ dpi: 图片转换分辨率
174
+
175
+ Returns:
176
+ 生成的图片文件路径列表
177
+ """
178
+ # 首先将PPT转换为PDF
179
+ pdf_path = self.ppt_to_pdf(ppt_path)
180
+
181
+ # 然后将PDF转换为图片
182
+ return self.pdf_to_images(pdf_path, dpi)
183
+
184
+ def get_slide_count_from_pdf(self, pdf_path: str) -> int:
185
+ """从PDF文件中获取页数(替代PPT幻灯片数量)。
186
+
187
+ Args:
188
+ pdf_path: PDF文件路径
189
+
190
+ Returns:
191
+ PDF页数
192
+ """
193
+ if not PDFIUM_AVAILABLE:
194
+ logger.warning("pypdfium2 未安装,无法获取页数")
195
+ return 0
196
+
197
+ try:
198
+ pdf = pdfium.PdfDocument(str(pdf_path))
199
+ return len(pdf)
200
+ except Exception as e:
201
+ logger.error(f"获取PDF页数失败: {e}")
202
+ return 0
203
+
204
+ def cleanup_cache(self, max_age_days: int = 30):
205
+ """清理旧的缓存文件。
206
+
207
+ Args:
208
+ max_age_days: 缓存文件的最大保留天数
209
+ """
210
+ import time
211
+
212
+ cutoff_time = time.time() - (max_age_days * 24 * 60 * 60)
213
+
214
+ for cache_subdir in [self.pdf_cache_dir, self.image_cache_dir]:
215
+ for file_path in cache_subdir.iterdir():
216
+ if file_path.is_file() and file_path.stat().st_mtime < cutoff_time:
217
+ try:
218
+ file_path.unlink()
219
+ logger.info(f"已清理旧缓存文件: {file_path}")
220
+ except Exception as e:
221
+ logger.error(f"清理失败 {file_path}: {e}")
222
+
223
+ def get_pdf_images_base64(self, pdf_path: str, scale: float = 4.0) -> List[str]:
224
+ """
225
+ 将PDF转换为base64编码的图片列表(用于文档理解)
226
+
227
+ Args:
228
+ pdf_path: PDF文件路径
229
+ scale: 渲染缩放比例,数值越大图片质量越高
230
+
231
+ Returns:
232
+ base64编码的图片字符串列表
233
+
234
+ Raises:
235
+ FileNotFoundError: 如果PDF文件不存在
236
+ RuntimeError: 如果转换失败或缺少pypdfium2
237
+ """
238
+ if not PDFIUM_AVAILABLE:
239
+ raise RuntimeError("pypdfium2 未安装。请运行: pip install pypdfium2")
240
+
241
+ pdf_path = Path(pdf_path)
242
+ if not pdf_path.exists():
243
+ raise FileNotFoundError(f"未找到PDF文件: {pdf_path}")
244
+
245
+ try:
246
+ logger.info(f"正在分析PDF文档并转换为base64图片: {pdf_path}")
247
+
248
+ # 打开PDF文档
249
+ pdf = pdfium.PdfDocument(str(pdf_path))
250
+ images = []
251
+
252
+ # 转换每一页为图片并编码为base64
253
+ for i in range(len(pdf)):
254
+ page = pdf[i]
255
+ image = page.render(scale=scale).to_pil()
256
+ buffered = BytesIO()
257
+ image.save(buffered, format="JPEG")
258
+ img_byte = buffered.getvalue()
259
+ img_base64 = base64.b64encode(img_byte).decode("utf-8")
260
+ images.append(img_base64)
261
+
262
+ logger.info(f"PDF已转换为 {len(images)} 张base64编码图片")
263
+ return images
264
+
265
+ except Exception as e:
266
+ raise RuntimeError(f"PDF文档分析失败: {str(e)}")