Skip to main content
Glama

PowerPoint Editor MCP Server

tool.py43.6 kB
#!/usr/bin/env python3 """ PowerPoint编辑器工具类 提供基础的PPT编辑功能,包括添加文本、图片、形状等 """ import logging from typing import Any, Dict, List, Optional, TYPE_CHECKING from pathlib import Path import json # 导入PowerPoint相关库 try: from pptx import Presentation from pptx.util import Inches, Pt from pptx.enum.shapes import MSO_SHAPE from pptx.dml.color import RGBColor except ImportError: raise ImportError("请安装python-pptx库: pip install python-pptx") if TYPE_CHECKING: from pptx.presentation import Presentation as PresentationType # 设置日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class PowerPointEditor: """PowerPoint编辑器类""" def __init__(self): self.current_presentation: Optional["PresentationType"] = None self.current_file_path: Optional[str] = None def create_presentation(self) -> Dict[str, Any]: """创建新的演示文稿""" try: self.current_presentation = Presentation() self.current_file_path = None return { "success": True, "message": "成功创建新的演示文稿", "slides_count": len(self.current_presentation.slides) } except Exception as e: return {"success": False, "error": str(e)} def open_presentation(self, file_path: str) -> Dict[str, Any]: """打开现有的演示文稿""" try: if not Path(file_path).exists(): return {"success": False, "error": f"文件不存在: {file_path}"} self.current_presentation = Presentation(file_path) self.current_file_path = file_path return { "success": True, "message": f"成功打开演示文稿: {file_path}", "slides_count": len(self.current_presentation.slides), "file_path": file_path } except Exception as e: return {"success": False, "error": str(e)} def save_presentation(self, file_path: Optional[str] = None) -> Dict[str, Any]: """保存演示文稿""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} save_path = file_path or self.current_file_path if not save_path: return {"success": False, "error": "请指定保存路径"} # 保存前验证过渡效果 transition_count = self._count_transitions() self.current_presentation.save(save_path) self.current_file_path = save_path return { "success": True, "message": f"成功保存演示文稿: {save_path}", "file_path": save_path, "slides_with_transitions": transition_count, "note": f"文件包含 {transition_count} 张有过渡效果的幻灯片" if transition_count > 0 else "文件不包含过渡效果" } except Exception as e: return {"success": False, "error": str(e)} def _count_transitions(self) -> int: """统计有过渡效果的幻灯片数量""" try: if not self.current_presentation: return 0 count = 0 namespaces = {'p': 'http://schemas.openxmlformats.org/presentationml/2006/main'} for slide in self.current_presentation.slides: slide_element = slide._element transition_elem = slide_element.find('.//p:transition', namespaces) if transition_elem is not None: count += 1 return count except: return 0 def add_slide(self, layout_index: int = 1) -> Dict[str, Any]: """添加新幻灯片""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} # 获取幻灯片布局 slide_layouts = self.current_presentation.slide_layouts if layout_index >= len(slide_layouts): layout_index = 1 # 默认使用标题和内容布局 layout = slide_layouts[layout_index] slide = self.current_presentation.slides.add_slide(layout) return { "success": True, "message": f"成功添加新幻灯片", "slide_index": len(self.current_presentation.slides) - 1, "total_slides": len(self.current_presentation.slides) } except Exception as e: return {"success": False, "error": str(e)} def add_text_box(self, slide_index: int, text: str, left: float = 1, top: float = 1, width: float = 8, height: float = 1, font_size: int = 18, font_color: str = "000000") -> Dict[str, Any]: """在指定幻灯片添加文本框""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} slides = self.current_presentation.slides if slide_index >= len(slides): return {"success": False, "error": f"幻灯片索引超出范围: {slide_index}"} slide = slides[slide_index] # 添加文本框 left_inches = Inches(left) top_inches = Inches(top) width_inches = Inches(width) height_inches = Inches(height) textbox = slide.shapes.add_textbox(left_inches, top_inches, width_inches, height_inches) text_frame = textbox.text_frame text_frame.text = text # 设置字体样式 paragraph = text_frame.paragraphs[0] font = paragraph.font font.size = Pt(font_size) # 设置字体颜色 try: rgb_color = RGBColor.from_string(font_color) font.color.rgb = rgb_color except: pass # 如果颜色格式不正确,使用默认颜色 return { "success": True, "message": f"成功在幻灯片 {slide_index} 添加文本框", "text": text, "position": {"left": left, "top": top, "width": width, "height": height} } except Exception as e: return {"success": False, "error": str(e)} def add_title_slide(self, title: str, subtitle: str = "") -> Dict[str, Any]: """添加标题幻灯片""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} # 使用标题幻灯片布局 title_slide_layout = self.current_presentation.slide_layouts[0] slide = self.current_presentation.slides.add_slide(title_slide_layout) # 设置标题 title_shape = slide.shapes.title if title_shape: title_shape.text = title # 设置副标题 if subtitle and len(slide.placeholders) > 1: try: subtitle_shape = slide.placeholders[1] subtitle_shape.text_frame.text = subtitle # type: ignore except (AttributeError, TypeError): pass # 如果无法设置副标题,忽略错误 return { "success": True, "message": "成功添加标题幻灯片", "slide_index": len(self.current_presentation.slides) - 1, "title": title, "subtitle": subtitle } except Exception as e: return {"success": False, "error": str(e)} def add_bullet_points(self, slide_index: int, title: str, bullet_points: List[str]) -> Dict[str, Any]: """添加带项目符号的内容幻灯片""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} slides = self.current_presentation.slides if slide_index >= len(slides): return {"success": False, "error": f"幻灯片索引超出范围: {slide_index}"} slide = slides[slide_index] # 设置标题 if slide.shapes.title: slide.shapes.title.text = title # 查找内容占位符 content_placeholder = None for shape in slide.placeholders: if shape.placeholder_format.idx == 1: # 内容占位符通常是索引1 content_placeholder = shape break if content_placeholder: try: text_frame = content_placeholder.text_frame # type: ignore text_frame.clear() # 清除现有内容 for i, point in enumerate(bullet_points): if i == 0: p = text_frame.paragraphs[0] else: p = text_frame.add_paragraph() p.text = point p.level = 0 # 设置为第一级项目符号 except (AttributeError, TypeError): pass # 如果无法设置内容,忽略错误 return { "success": True, "message": f"成功在幻灯片 {slide_index} 添加项目符号内容", "title": title, "bullet_points": bullet_points } except Exception as e: return {"success": False, "error": str(e)} def add_image(self, slide_index: int, image_path: str, left: float = 1, top: float = 2, width: Optional[float] = None, height: Optional[float] = None) -> Dict[str, Any]: """在幻灯片中添加图片""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} if not Path(image_path).exists(): return {"success": False, "error": f"图片文件不存在: {image_path}"} slides = self.current_presentation.slides if slide_index >= len(slides): return {"success": False, "error": f"幻灯片索引超出范围: {slide_index}"} slide = slides[slide_index] # 添加图片 left_inches = Inches(left) top_inches = Inches(top) if width and height: width_inches = Inches(width) height_inches = Inches(height) pic = slide.shapes.add_picture(image_path, left_inches, top_inches, width_inches, height_inches) else: pic = slide.shapes.add_picture(image_path, left_inches, top_inches) return { "success": True, "message": f"成功在幻灯片 {slide_index} 添加图片", "image_path": image_path, "position": {"left": left, "top": top, "width": width, "height": height} } except Exception as e: return {"success": False, "error": str(e)} def add_shape(self, slide_index: int, shape_type: str, left: float = 1, top: float = 1, width: float = 2, height: float = 1, fill_color: str = "0066CC") -> Dict[str, Any]: """添加形状""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} slides = self.current_presentation.slides if slide_index >= len(slides): return {"success": False, "error": f"幻灯片索引超出范围: {slide_index}"} slide = slides[slide_index] # 形状类型映射 shape_map = { "rectangle": MSO_SHAPE.RECTANGLE, "oval": MSO_SHAPE.OVAL, "triangle": MSO_SHAPE.ISOSCELES_TRIANGLE, "diamond": MSO_SHAPE.DIAMOND, "pentagon": MSO_SHAPE.REGULAR_PENTAGON, "hexagon": MSO_SHAPE.HEXAGON, "star": MSO_SHAPE.STAR_5_POINT, "arrow": MSO_SHAPE.BLOCK_ARC } if shape_type.lower() not in shape_map: return {"success": False, "error": f"不支持的形状类型: {shape_type}"} # 添加形状 left_inches = Inches(left) top_inches = Inches(top) width_inches = Inches(width) height_inches = Inches(height) shape = slide.shapes.add_shape( shape_map[shape_type.lower()], left_inches, top_inches, width_inches, height_inches ) # 设置填充颜色 try: rgb_color = RGBColor.from_string(fill_color) shape.fill.solid() shape.fill.fore_color.rgb = rgb_color except: pass # 如果颜色格式不正确,使用默认颜色 return { "success": True, "message": f"成功在幻灯片 {slide_index} 添加 {shape_type} 形状", "shape_type": shape_type, "position": {"left": left, "top": top, "width": width, "height": height}, "fill_color": fill_color } except Exception as e: return {"success": False, "error": str(e)} def get_presentation_info(self) -> Dict[str, Any]: """获取当前演示文稿信息""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} slides_info = [] for i, slide in enumerate(self.current_presentation.slides): slide_info = { "index": i, "shapes_count": len(slide.shapes), "has_title": bool(slide.shapes.title and slide.shapes.title.text), "title": slide.shapes.title.text if slide.shapes.title else "" } slides_info.append(slide_info) return { "success": True, "file_path": self.current_file_path, "slides_count": len(self.current_presentation.slides), "slides": slides_info } except Exception as e: return {"success": False, "error": str(e)} def delete_slide(self, slide_index: int) -> Dict[str, Any]: """删除指定幻灯片""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} slides = self.current_presentation.slides if slide_index >= len(slides): return {"success": False, "error": f"幻灯片索引超出范围: {slide_index}"} # 获取要删除的幻灯片 slide_to_remove = slides[slide_index] # 删除幻灯片的正确方法 # 直接从slides集合中删除 xml_slides = self.current_presentation.part._element.sldIdLst xml_slides.remove(xml_slides[slide_index]) slides._sldIdLst.remove(slides._sldIdLst[slide_index]) return { "success": True, "message": f"成功删除幻灯片 {slide_index}", "remaining_slides": len(self.current_presentation.slides) } except Exception as e: return {"success": False, "error": str(e)} def duplicate_slide(self, slide_index: int) -> Dict[str, Any]: """复制指定幻灯片""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} slides = self.current_presentation.slides if slide_index >= len(slides): return {"success": False, "error": f"幻灯片索引超出范围: {slide_index}"} # 获取要复制的幻灯片 source_slide = slides[slide_index] # 复制幻灯片布局 slide_layout = source_slide.slide_layout new_slide = slides.add_slide(slide_layout) # 复制所有形状 for shape in source_slide.shapes: if not shape.is_placeholder: # 复制非占位符形状 self._copy_shape(shape, new_slide) return { "success": True, "message": f"成功复制幻灯片 {slide_index}", "new_slide_index": len(slides) - 1, "total_slides": len(slides) } except Exception as e: return {"success": False, "error": str(e)} def move_slide(self, from_index: int, to_index: int) -> Dict[str, Any]: """移动幻灯片位置""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} slides = self.current_presentation.slides if from_index >= len(slides) or to_index >= len(slides): return {"success": False, "error": "幻灯片索引超出范围"} if from_index == to_index: return {"success": True, "message": "幻灯片位置未改变"} # 简化的移动方法:复制幻灯片到新位置,然后删除原位置 # 这是一个更安全的方法,避免直接操作XML # 获取源幻灯片的布局 source_slide = slides[from_index] slide_layout = source_slide.slide_layout # 在目标位置创建新幻灯片 if to_index >= len(slides): new_slide = slides.add_slide(slide_layout) else: # 在指定位置插入需要更复杂的操作,这里简化处理 new_slide = slides.add_slide(slide_layout) # 复制内容(简化版本) for shape in source_slide.shapes: if not shape.is_placeholder: self._copy_shape(shape, new_slide) # 删除原幻灯片(如果新幻灯片在后面) if from_index < len(slides) - 1: # 由于添加了新幻灯片,原索引可能需要调整 actual_from_index = from_index if to_index > from_index else from_index xml_slides = self.current_presentation.part._element.sldIdLst xml_slides.remove(xml_slides[actual_from_index]) slides._sldIdLst.remove(slides._sldIdLst[actual_from_index]) return { "success": True, "message": f"成功将幻灯片从位置 {from_index} 移动到位置 {to_index}", "total_slides": len(slides) } except Exception as e: return {"success": False, "error": str(e)} def add_table(self, slide_index: int, rows: int, cols: int, left: float = 1, top: float = 2, width: float = 8, height: float = 4) -> Dict[str, Any]: """在幻灯片中添加表格""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} slides = self.current_presentation.slides if slide_index >= len(slides): return {"success": False, "error": f"幻灯片索引超出范围: {slide_index}"} slide = slides[slide_index] # 添加表格 left_inches = Inches(left) top_inches = Inches(top) width_inches = Inches(width) height_inches = Inches(height) table = slide.shapes.add_table(rows, cols, left_inches, top_inches, width_inches, height_inches) return { "success": True, "message": f"成功在幻灯片 {slide_index} 添加 {rows}x{cols} 表格", "rows": rows, "cols": cols, "position": {"left": left, "top": top, "width": width, "height": height} } except Exception as e: return {"success": False, "error": str(e)} def set_table_cell_text(self, slide_index: int, table_index: int, row: int, col: int, text: str) -> Dict[str, Any]: """设置表格单元格文本""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} slides = self.current_presentation.slides if slide_index >= len(slides): return {"success": False, "error": f"幻灯片索引超出范围: {slide_index}"} slide = slides[slide_index] # 查找表格 - 使用更安全的方法 tables = [] for shape in slide.shapes: try: # 检查是否是表格形状 if hasattr(shape, 'table'): table_obj = getattr(shape, 'table', None) if table_obj is not None: tables.append(shape) except: continue if table_index >= len(tables): return {"success": False, "error": f"表格索引超出范围: {table_index}"} table_shape = tables[table_index] table = getattr(table_shape, 'table') if row >= len(table.rows) or col >= len(table.columns): return {"success": False, "error": "单元格位置超出表格范围"} # 设置单元格文本 cell = table.cell(row, col) cell.text = text return { "success": True, "message": f"成功设置表格单元格 ({row}, {col}) 的文本", "text": text } except Exception as e: return {"success": False, "error": str(e)} def set_slide_background_color(self, slide_index: int, color: str) -> Dict[str, Any]: """设置幻灯片背景颜色""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} slides = self.current_presentation.slides if slide_index >= len(slides): return {"success": False, "error": f"幻灯片索引超出范围: {slide_index}"} slide = slides[slide_index] # 设置背景颜色 background = slide.background fill = background.fill fill.solid() try: rgb_color = RGBColor.from_string(color) fill.fore_color.rgb = rgb_color except: return {"success": False, "error": f"无效的颜色格式: {color}"} return { "success": True, "message": f"成功设置幻灯片 {slide_index} 的背景颜色", "color": color } except Exception as e: return {"success": False, "error": str(e)} def add_hyperlink(self, slide_index: int, shape_index: int, url: str, display_text: Optional[str] = None) -> Dict[str, Any]: """为形状添加超链接""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} slides = self.current_presentation.slides if slide_index >= len(slides): return {"success": False, "error": f"幻灯片索引超出范围: {slide_index}"} slide = slides[slide_index] if shape_index >= len(slide.shapes): return {"success": False, "error": f"形状索引超出范围: {shape_index}"} shape = slide.shapes[shape_index] # 添加超链接 try: # 类型安全的文本框操作 if hasattr(shape, 'text_frame'): text_frame = getattr(shape, 'text_frame') if text_frame is not None: if display_text: text_frame.text = display_text if not text_frame.paragraphs: paragraph = text_frame.add_paragraph() else: paragraph = text_frame.paragraphs[0] if not paragraph.runs: run = paragraph.add_run() else: run = paragraph.runs[0] run.hyperlink.address = url else: # 其他形状类型 if hasattr(shape, 'click_action'): shape.click_action.hyperlink.address = url else: return {"success": False, "error": "形状不支持超链接"} else: # 其他形状类型 if hasattr(shape, 'click_action'): shape.click_action.hyperlink.address = url else: return {"success": False, "error": "形状不支持超链接"} except Exception as e: return {"success": False, "error": f"添加超链接失败: {str(e)}"} return { "success": True, "message": f"成功为幻灯片 {slide_index} 的形状 {shape_index} 添加超链接", "url": url, "display_text": display_text } except Exception as e: return {"success": False, "error": str(e)} def set_text_formatting(self, slide_index: int, shape_index: int, font_name: Optional[str] = None, font_size: Optional[int] = None, font_color: Optional[str] = None, bold: Optional[bool] = None, italic: Optional[bool] = None, underline: Optional[bool] = None) -> Dict[str, Any]: """设置文本格式""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} slides = self.current_presentation.slides if slide_index >= len(slides): return {"success": False, "error": f"幻灯片索引超出范围: {slide_index}"} slide = slides[slide_index] if shape_index >= len(slide.shapes): return {"success": False, "error": f"形状索引超出范围: {shape_index}"} shape = slide.shapes[shape_index] try: if not hasattr(shape, 'text_frame'): return {"success": False, "error": "形状不支持文本框"} text_frame = getattr(shape, 'text_frame') if text_frame is None: return {"success": False, "error": "文本框不可用"} if not hasattr(text_frame, 'paragraphs'): return {"success": False, "error": "文本框没有段落属性"} paragraphs = text_frame.paragraphs if not paragraphs or len(paragraphs) == 0: return {"success": False, "error": "没有可用的文本段落"} paragraph = paragraphs[0] if not hasattr(paragraph, 'font'): return {"success": False, "error": "段落没有字体属性"} font = paragraph.font if font is None: return {"success": False, "error": "无法获取字体对象"} # 设置字体属性 if font_name: font.name = font_name if font_size: font.size = Pt(font_size) if font_color: try: rgb_color = RGBColor.from_string(font_color) font.color.rgb = rgb_color except: return {"success": False, "error": f"无效的颜色格式: {font_color}"} if bold is not None: font.bold = bold if italic is not None: font.italic = italic if underline is not None: font.underline = underline except Exception as e: return {"success": False, "error": f"字体设置失败: {str(e)}"} return { "success": True, "message": f"成功设置幻灯片 {slide_index} 形状 {shape_index} 的文本格式", "formatting": { "font_name": font_name, "font_size": font_size, "font_color": font_color, "bold": bold, "italic": italic, "underline": underline } } except Exception as e: return {"success": False, "error": str(e)} def get_slide_shapes_info(self, slide_index: int) -> Dict[str, Any]: """获取幻灯片中所有形状的信息""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} slides = self.current_presentation.slides if slide_index >= len(slides): return {"success": False, "error": f"幻灯片索引超出范围: {slide_index}"} slide = slides[slide_index] shapes_info = [] for i, shape in enumerate(slide.shapes): shape_info = { "index": i, "shape_type": str(shape.shape_type), "name": shape.name, "left": shape.left.inches if hasattr(shape.left, 'inches') else 0, "top": shape.top.inches if hasattr(shape.top, 'inches') else 0, "width": shape.width.inches if hasattr(shape.width, 'inches') else 0, "height": shape.height.inches if hasattr(shape.height, 'inches') else 0, "has_text": hasattr(shape, 'text_frame') and getattr(shape, 'text_frame', None) is not None, "text": getattr(shape, 'text', "") if hasattr(shape, 'text') else "" } shapes_info.append(shape_info) return { "success": True, "slide_index": slide_index, "shapes_count": len(shapes_info), "shapes": shapes_info } except Exception as e: return {"success": False, "error": str(e)} def _copy_shape(self, source_shape, target_slide): """辅助方法:复制形状(简化版本)""" try: # 这是一个简化的形状复制方法 # 对于复杂的形状复制,可能需要更详细的实现 if hasattr(source_shape, 'text_frame') and source_shape.text_frame: # 复制文本框 textbox = target_slide.shapes.add_textbox( source_shape.left, source_shape.top, source_shape.width, source_shape.height ) textbox.text_frame.text = source_shape.text_frame.text except: # 如果复制失败,忽略该形状 pass def set_slide_transition(self, slide_index: int, transition_type: str = "fade", duration: float = 1.0, advance_on_click: bool = True, advance_after_time: Optional[float] = None) -> Dict[str, Any]: """设置幻灯片过渡效果""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} slides = self.current_presentation.slides if slide_index >= len(slides): return {"success": False, "error": f"幻灯片索引超出范围: {slide_index}"} slide = slides[slide_index] # 支持的过渡类型 supported_transitions = ["none", "fade", "push", "wipe", "split", "zoom", "blinds", "dissolve"] if transition_type.lower() not in supported_transitions: return {"success": False, "error": f"不支持的过渡类型: {transition_type}。支持的类型: {', '.join(supported_transitions)}"} # 获取幻灯片的XML元素 slide_element = slide._element # 定义命名空间 namespaces = {'p': 'http://schemas.openxmlformats.org/presentationml/2006/main'} # 移除现有的过渡元素(如果存在) existing_transition = slide_element.find('.//p:transition', namespaces) if existing_transition is not None: slide_element.remove(existing_transition) # 创建新的过渡元素(如果不是none) if transition_type.lower() != "none": try: from lxml import etree except ImportError: return {"success": False, "error": "需要安装lxml库: pip install lxml"} # 创建过渡XML字符串 transition_xml = self._create_transition_xml(transition_type, duration, advance_on_click, advance_after_time) # 解析XML并插入到幻灯片中 parser = etree.XMLParser(ns_clean=True, recover=True) transition_elem = etree.fromstring(transition_xml.encode('utf-8'), parser) # 将过渡元素插入到符合规范的位置 # p:transition 应该在 p:cSld 和 p:clrMapOvr 之间 color_map_override = slide_element.find('.//p:clrMapOvr', namespaces) if color_map_override is not None: color_map_override.addprevious(transition_elem) else: # 如果没有 p:clrMapOvr,则追加到末尾 slide_element.append(transition_elem) # 验证插入是否成功 verification_elem = slide_element.find('.//p:transition', namespaces) if verification_elem is None: return {"success": False, "error": "过渡效果XML插入失败"} return { "success": True, "message": f"成功设置幻灯片 {slide_index} 的过渡效果", "transition_type": transition_type, "duration": duration, "advance_on_click": advance_on_click, "advance_after_time": advance_after_time, "verification": "过渡效果已验证插入成功" if transition_type.lower() != "none" else "已移除过渡效果" } except Exception as e: return {"success": False, "error": str(e)} def _create_transition_xml(self, transition_type: str, duration: float, advance_on_click: bool, advance_after_time: Optional[float]) -> str: """创建过渡效果的XML字符串""" # 设置过渡速度 if duration <= 0.5: speed = "fast" elif duration <= 2.0: speed = "med" else: speed = "slow" # 基础过渡元素 transition_attrs = f'spd="{speed}"' if advance_on_click: transition_attrs += ' advClick="1"' else: transition_attrs += ' advClick="0"' if advance_after_time is not None: advance_time_ms = int(advance_after_time * 1000) transition_attrs += f' advTm="{advance_time_ms}"' # 根据过渡类型创建相应的XML transition_content = "" if transition_type.lower() == "fade": transition_content = '<p:fade/>' elif transition_type.lower() == "push": transition_content = '<p:push dir="l"/>' elif transition_type.lower() == "wipe": transition_content = '<p:wipe dir="l"/>' elif transition_type.lower() == "zoom": transition_content = '<p:zoom/>' elif transition_type.lower() == "split": transition_content = '<p:split orient="horz" dir="out"/>' elif transition_type.lower() == "blinds": transition_content = '<p:blinds dir="horz"/>' elif transition_type.lower() == "dissolve": transition_content = '<p:dissolve/>' else: # 默认使用fade transition_content = '<p:fade/>' # 完整的XML字符串 xml_string = f'''<p:transition xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" {transition_attrs}> {transition_content} </p:transition>''' return xml_string def apply_transition_to_all_slides(self, transition_type: str = "fade", duration: float = 1.0) -> Dict[str, Any]: """为所有幻灯片应用统一的过渡效果""" try: if not self.current_presentation: return {"success": False, "error": "没有打开的演示文稿"} slides = self.current_presentation.slides if len(slides) == 0: return {"success": False, "error": "演示文稿中没有幻灯片"} success_count = 0 failed_slides = [] # 为每张幻灯片设置过渡效果 for i in range(len(slides)): result = self.set_slide_transition(i, transition_type, duration, True, None) if result.get("success"): success_count += 1 else: failed_slides.append(i) if success_count == len(slides): return { "success": True, "message": f"成功为所有 {len(slides)} 张幻灯片设置了 '{transition_type}' 过渡效果", "transition_type": transition_type, "duration": duration, "slides_processed": len(slides) } else: return { "success": True, "message": f"为 {success_count}/{len(slides)} 张幻灯片设置了过渡效果", "transition_type": transition_type, "duration": duration, "slides_processed": success_count, "failed_slides": failed_slides, "warning": f"有 {len(failed_slides)} 张幻灯片设置失败" } except Exception as e: return {"success": False, "error": str(e)} def get_available_transitions(self) -> Dict[str, Any]: """获取可用的过渡效果列表""" try: # 只返回实际支持的过渡效果 transitions = [ {"name": "none", "description": "无过渡效果"}, {"name": "fade", "description": "淡入淡出 - 推荐用于专业演示"}, {"name": "push", "description": "推入 - 动感十足"}, {"name": "wipe", "description": "擦除 - 简洁流畅"}, {"name": "split", "description": "分割 - 创意效果"}, {"name": "zoom", "description": "缩放 - 突出重点"}, {"name": "blinds", "description": "百叶窗 - 经典效果"}, {"name": "dissolve", "description": "溶解 - 柔和过渡"} ] return { "success": True, "transitions": transitions, "total_count": len(transitions), "note": "这些动画效果可以让您的演示文稿更加生动有趣", "recommendation": "推荐使用 'fade' 效果,适合大多数专业演示场合" } except Exception as e: return {"success": False, "error": str(e)} def make_presentation_professional(self) -> Dict[str, Any]: """一键让演示文稿变得专业 - 添加淡入淡出过渡效果""" return self.apply_transition_to_all_slides("fade", 1.0) def add_smooth_transitions(self) -> Dict[str, Any]: """为演示文稿添加流畅的过渡动画""" return self.apply_transition_to_all_slides("fade", 0.8) def add_dynamic_effects(self) -> Dict[str, Any]: """为演示文稿添加动感效果""" return self.apply_transition_to_all_slides("push", 1.2) def generate_outline_for_topic(self, topic: str) -> Dict[str, Any]: """根据主题,生成一个结构化的、用于创建演示文稿的JSON大纲。""" try: # 为了演示,我们在这里生成一个硬编码的示例大纲。 # 在实际应用中,这里可以是对真正LLM服务的API调用。 outline_data = { "slides": [ { "title": f"关于 {topic} 的深入探讨", "subtitle": "由AI辅助生成" }, { "title": "介绍与背景", "content": [ f"{topic} 的定义与重要性", "相关的历史发展", "本次讨论的主要范围" ] }, { "title": "核心要点分析", "content": [ "第一个关键方面", "第二个关键方面,并提供示例", "第三个关键方面的深入分析" ] }, { "title": "案例研究或实际应用", "content": [ f"一个关于 {topic} 的真实世界案例", "从案例中得到的启示", "如何将这些应用到实践中" ] }, { "title": "总结与展望", "content": [ f"对 {topic} 的核心内容进行总结", "未来的发展趋势", "问答环节" ] } ] } outline_json = json.dumps(outline_data, ensure_ascii=False, indent=2) return { "success": True, "message": f"成功为主题 '{topic}' 生成大纲。", "outline_json": outline_json } except Exception as e: logger.error(f"为主题 '{topic}' 生成大纲时出错: {e}") return {"success": False, "error": str(e)}

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Weichenleeeee123/ppt-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server