开发者

利用Python自动化处理PPT样式与结构的解决方案

目录
  • 引言
  • 代码功能概述
    • 核心功能模块
  • 代码详解
    • 1. 提取PPT样式(extract_ppt_with_style)
    • 2. 应用样式生成PPT(apply_styles_to_ppt)
    • 3. 幻灯片结构管理
      • 3.1 复制与插入幻灯片
      • 3.2 删除幻灯片
      • 3.3 动态扩展/压缩PPT页数
  • 使用示例
    • 场景1:生成符合样式的PPT
      • 场景2:动态调整PPT页数
      • 应用场景
        • 总结

          引言

          PowerPoint(PPT)是职场中常用的办公工具,但手动设计和调整样式往往耗时耗力。本文将介绍一套基于python的自动化解决方案,通过代码实现以下功能:

          • 提取PPT样式:将PPT的文本格式、颜色、布局等信息保存为jsON文件。
          • 应用样式到模板:根据JSON定义的样式生成新PPT。
          • 幻灯片增删与复制:灵活调整PPT结构,满足动态内容需求。

          代码功能概述

          核心功能模块

          1. 样式提取与保存

            • 函数:extract_ppt_with_style(ppt_path, output_json)
            • 功能:遍历PPT的每一页,提取文本框的字体、颜色、段落对齐方式等样式信息,并保存为结构化的JSON文件。
          2. 样式应用与生成

            • 函数:apply_styles_to_ppt(template_path, json_path, output_pptx, data_json_llm)
            • 功能:根据JSON定义的样式,将文本内容和格式应用到指定模板,生成符合要求的新PPT。
          3. 幻灯片结构管理

            • 函数:copy_slide_and_insert_afterdelete_slidecopy_ppt
            • 功能:复制、删除幻灯片,并根据需求动态调整中间页的数量(如扩展或压缩内容页)。

          代码详解

          1. 提取PPT样式(extract_ppt_with_style)

          def extract_ppt_with_style(ppt_path, output_jsogtTJomn):
              prs = Presentation(ppt_path)
              data = []
              for slide_idx, slide in enumerate(prs.slides):
                  slide_data = {
                      "slide_number": slide_idx + 1,
                      "shapes": []
                  }
                  for shape in slide.shapes:
                      if not shape.has_text_frame:
                          continue  # 跳过非文本形状
                      text_frame = shape.text_frame
                      text_info = {
                          "shape_name": shape.name,
                          "paragraphs": []
                      }
                      for paragraph in text_frame.paragraphs:
                          para_info = {
                              "alignment": str(paragraph.alignment),
                              "runs": []
                          }
                          for run in paragraph.runs:
                              run_info = {
                                  "text": run.text,
                                  "font": {
                                      "name": run.font.name,
                                      "size": str(run.font.size),
                                      "bold": run.font.bold,
                                      "italic": run.font.italic,
                                      "color": {
                                          "type": "theme" if run.font.color.type == MSO_THEME_COLOR else "rgb",
                                          "theme_color": run.font.color.theme_color,
                                          "rgb": (run.font.color.rgb[0], run.font.color.rgb[1], run.font.color.rgb[2]) if run.font.color.rgb else None
                                      }
                                  }
                              }
                              para_info["runs"].append(run_info)
                          text_info["paragraphs"].append(para_info)
                      slide_data["shapes"].append(text_info)
                  data.append(slide_data)
              # 保存并压缩JSON
              with open(output_json, 'w', encoding='utf-8') as f:
                  json.dump(data, f, indent=2, ensure_ascii=False)
              data = json_compress(data)
              with open("compress_" + output_json, 'w', encoding='utf-8') as f:
                  json.dump(data, f, indent=2, ensure_ascii=False)
              return data
          
          • 关键点
            • 样式解析:记录字体名称、大小、粗体、斜体、颜色(主题色或RGB值)。
            • 结构化存储:JSON中每一页(slide)包含多个形状(shapes),每个形状包含段落(paragraphs)和文本片段(runs)。
            • 压缩优化json_compress 函数简化冗余数据(如通用形状名称),提升存储效率。

          2. 应用样式生成PPT(apply_styles_to_ppt)

          def apply_styles_to_ppt(template_path, json_path, output_pptx, data_json_llm):
              with open(json_path, 'r', encoding='utf-8') as f:
                  data = json.load(f)
              prs = Presentation(template_path)
              for slide_idx, slide in enumerate(prs.slides):
                 js for shape_idx, shape in enumerate(slide.shapes):
                      if not shape.has_text_frame:
                          continue
                      text_frame = shape.text_frame
                      for paragraph_idx, paragraph in enumerate(text_frame.paragraphs):
                          for run_idx, run in enumerate(paragraph.runs):
                              run_info = data[slide_idx]["shapes"][shape_idx]["paragraphs"][paragraph_idx]["runs"][run_idx]
                              # 应用文本内容
                              run.text = data_json_llm[slide_idx]["shapes"].pop()["paragraphs"]
                              # 应用样式
                              run.font.name = run_info["font"]["name"]
                              run.font.bold = run_info["font"]["bold"]
                              run.font.italic = run_info["font"]["italic"]
                              # 处理颜色
                              color_data = run_info["font"]["color"]
                              if color_data["type"] == "rgb":
                                  r, g, b = color_data["rgb"]
                                  run.font.color.rgb = RGBColor(r, g, b)
                              elif color_data["type"] == "theme":
                                  theme_color = getattr(MSO_THEME_COLOR, color_data["theme_color"], MSO_THEME_COLOR.ACCENT_1)
                                  run.font.color.theme_color = theme_color
              prs.save(output_pptx)
          
          • 关键点
            • 样式复用:从JSON中读取字体、颜色等信息,直接应用到模板PPT的对应位置。
            • 动态内容替换:通过 data_json_llm 参数,可结合LLM(如GPT)生成的文本内容动态填充PPT。

          3. 幻灯片结构管理

          3.1 复制与插入幻灯片

          def copy_slide_and_insert_after(prs, source_index, target_index):
              source_slide = prs.slides[source_index]
              new_slide = prs.slides.add_slide(source_slide.slide_layout)
              # 复制形状和关系
              for shape in source_slide.shapes:
                  new_el = deepcopy(shape.element)
                  new_slide.shapes._spTree.insert_element_before(new_el, 'p:extLst')
              # 调整位置
              slides = list(prs.slides._sldIdLst)
              new_slide_id = slides.pop()
              slides.insert(target_index + 1, new_slide_id)
              prs.slides._sldIdLst[:] = slides
          
          • 功能:复制指定幻灯片并插入到目标位置后,保持布局和元素一致性。

          3.2 删除幻灯片

          def delete_slide(prs, slide_index):
              if slide_index < 0 or slide_index >= len(prs.slides):
                  print("无效的幻灯片索引")
                  return
              XML_slides = list(prs.slides._sldIdLst)
              slides_id_to_delete = xml_slides[slide_index]
              prs.slides._sldIdLst.remove(slides_id_to_delete)
          
          • 功能:通过移除幻灯片ID实现删除,避免直接操作可能导致的格式错误。

          3.3 动态扩展/压缩PPT页数

          def copy_ppt(pages, template_path="template.pptx", modified_path="modified.pptx"):
              prs = Presentation(template_path)
              copy_pages = pages - 2  # 排除首尾固定页
              center_pages = len(prs.slides) - 2
              if copy_pages < center_pages:
                  # 删除多余页
                  for _ in range(center_pages - copy_pages):
                      delete_slide(prs, len(prs.slides)-1)
              else:
                  # 复制中间页
                  n = (copy_pages // center_pages) * center_pages
                  for _ in range(n):
                      for i in range(1, center_pages+1):
                          copy_slide_and_insert_after(prs, i, i)
              prs.save(modified_path)
          
          • 应用场景:根据需求动态调整中间页的数量(如扩展到5页或压缩到3页),保持首尾页固定。

          使用示例

          场景1:生成符合样式的PPT

          # 1. 提取原始PPT的样式
          extract_ppt_with_style("template.pptx", "output_styles.json")
          
          # 2. 生成新内容(例如通过LLM)
          llm_json = [...]  # LLM生成的文本内容
          
          # 3. 应用样式生成最终PPT
          apply_styles_to_ppt("template.pptx", "output_styles.json", "new_ppt.pptx", llm_json)
          

          场景2:动态调整PPT页数

          # 假设原始模板有5页(首尾固定,中间3页)
          copy_ppt(pages=7, template_path="template.pptx")  # 最终生成7页:1(首)+5(中间复制)+1(尾)
          

          应用场景

          • 企业报告自动化:根据数据动态生成季度报告,保持统一格式。
          • 培训材料生成:批量创建多套PPT,仅需调整中间内容页。
          • 营销素材管理:快速复制产品介绍模板,替换文本和样式。

          总结

          本文提供的代码库实现了从PPT样式提取、动态内容生成到结构管理的全流程自动化。开发者可通过以下方式进一步优化:

          • 集成LLM:将文本生成部分与GPT等模型结合,实现从内容到样式的全自动化。
          • 图形处理:扩展对图片、图表样式的解析与应用。
          • 用户界面:封装为GUI工具,降低使用门槛。

          通过这种方式,企业可大幅减少PPT制作时间,专注于内容创新而非格式调整。

          from pptx import Presentation
          from pptx.enum.dml import MSO_THEME_COLOR
          from pptx.dml.color import RGBColor
          from copy import deepcopy
          import json
          
          
          def extract_ppt_with_style(ppt_path, output_json):
              prs = Presentation(ppt_path)
              data = []
          
              for slide_idx, slide in enumerate(prs.slides):
                  slide_data = {
                      "slide_number": slide_idx + 1,
                      "shapes": []
                  }
                  for shape in slide.shapes:
                      if not shape.has_text_frame:
                          continue  # 跳过非文本形状
          
                      text_frame = shape.text_frame
                      text_info = {
                          "shape_name": shape.name,
                          "paragraphs": []
                      }
          
                      for paragraph in text_frame.paragraphs:
                          para_info = {
                              "alignment": str(paragraph.alignment),
                              "runs": []
                          }
                          for run in paragraph.runs:
                              run_info = {
                                  "text": run.text,
                                  "font": {
                                      "name": run.font.name,
                                      "size": str(run.font.size) if run.font.size else None,
                                      "bold": run.font.bold,
                                      "italic": run.font.italic,
                                      "color": {
                                          "type": "theme" if run.font.color.type == MSO_THEME_COLOR else "rgb",
                                          "theme_color"python: run.font.color.theme_color,
                                          "rgb": (run.font.color.rgb[0], run.font.color.rgb[1],
                                                  run.font.color.rgb[2]) if run.font.color.rgb else None
                                      }
                                  },
                                  # "highlight_color": str(run.highlight_color)  # 修改:从 run 而非 run.font 获取
                              }
                              para_info["runs"].append(run_info)
                          text_info["paragraphs"].append(para_info)
                      slide_data["shapes"].append(text_info)
                  data.append(slide_dphpata)
          
              with open(output_json, 'w', encoding='utf-8') as f:
                  json.dump(data, f, indent=2, ensure_ascii=False)
              data = json_compress(data)
          
              with open("compress" + "_" + output_json, 'w', encoding='utf-8') as f:
                  json.dump(data, f, indent=2, ensure_ascii=False)
              return data
          
          
          def apply_styles_to_ppt(template_path, json_path, output_pptx, data_json_llm):
              with open(json_path, 'r', encoding='utf-8') as f:
                  data = json.load(f)
          
              prs = Presentation(template_path)
          
              for slide_idx, slide in enumerate(prs.slides):
          
                  for shape_idx, shape in enumerate(slide.shapes):
                      if not shape.has_text_frame:
                          continue  # 跳过非文本形状
          
                      text_frame = shape.text_frame
          
                      for paragraph_idx, paragraph in enumerate(text_frame.paragraphs):
          
                          for run_idx, run in enumerate(paragraph.runs):
                              run_info = data[slide_idx]["shapes"][shape_idx]["paragraphs"][paragraph_idx]["runs"][run_idx]
                              text = data_json_llm[slide_idx]["shapes"].pop()
                              # run.text = run_info["text"]
                              run.text = text["paragraphs"]
                              run.font.name = run_info["font"]["name"]
                              # run.font.size = run_info["font"]["size"]
                              run.font.bold = run_info["font"]["bold"]
                              # run.font.size = run_info["font"]["size"]
                              run.font.italic = run_info["font"]["italic"]
          
                              # 假设 run_data 是从 JSON 中读取的字典
                              color_data = run_info["font"]["color"]
          
                              if color_data["type"] == "rgb":
                                  # 解析 RGB 值
                                  r_str, g_str, b_str = color_data["rgb"]
                                  r = r_str
                                  g = g_str
                                  b = b_str
                                  run.font.color.rgb = RGBColor(r, g, b)
                              elif color_data["type"] == "hex":
                                  # 解析十六进制颜色
                                  hex_color = color_data["hex"].lstrip("#")
                                  r = int(hex_color[0:2], 16)
                                  g = int(hex_color[2:4], 16)
                                  b = int(hex_color[4:6], 16)
                                  run.font.color.rgb = RGBColor(r, g, b)
                              elif color_data["type"] == "theme":
                                  # 使用主题颜色(如 MSO_THEME_COLOR.ACCENT_1)
                                  theme_color_name = color_data["theme_color"]
                                  theme_color = getattr(MSO_THEME_COLOR, theme_color_name, MSO_THEME_COLOR.ACCENT_1)
                                  run.font.color.theme_color = theme_color
                              else:
                                  # 默认颜色(黑色)
                                  run.font.color.rgb = RGBColor(0, 0, 0)
          
              prs.save(output_pptx)
          
          
          def json_compress(json_data):
              for slide in json_data:
                  for shape in slide["shapes"]:
                      if "Shape" in shape["shape_name"]:
                          shape["paragraphs"] = {}
                      else:
                          for paragraph in shape["paragraphs"]:
                              for run in paragraph["runs"]:
                                  shape["paragraphs"] = run["text"]
              json_data_new = []
              for slide in json_data:
                  shapes = {"shapes": [], 'slide_number': slide['slide_number']}
                  for shape in slide["shapes"]:
                      if "Shape" in shape["shape_name"]:
                          shape["paragraphs"] = {}
                      else:
                          shapes["shapes"].append(shape)
                  json_data_new.append(shapes)
          
              return json_data_new
          
          
          def copy_slide_and_insert_after(prs, source_index, target_index):
              """
              复制源幻灯片并将其插入到目标幻灯片的后面。
          
              :param prs: Presentation 对象
              :param source_index: 源幻灯片的索引(从0开始)
              :param target_index: 目标幻灯片的索引(新幻灯片将插入到其后面)
              """
              # 获取源幻灯片
              source_slide = prs.slides[source_index]
          
              # 创建新幻灯片(使用相同的布局)
              new_slide_layout = source_slide.slide_layout
              new_slide = prs.slides.add_slide(new_slide_layout)
          
              # 复制所有形状(包括文本框、图片、图表等)
              for shape in source_slide.shapes:
                  el = shape.element
                  new_el = deepcopy(el)
                  new_slide.shapes._spTree.insert_element_before(new_el, 'p:extLst')
          
              # 复制关系(如超链接、注释等)
              for rel in source_slide.part.rels.values():
                  if "notesSlide" not in rel.reltype:  # 排除注释页
                      # 使用 relate_to 方法而不是 add 方法
                      new_slide.part.relate_to(
                          rel._target,
                          rel.reltype
                      )
          
              # 调整幻灯片顺序:将新幻灯片移动到目标位置的后面
              slides = list(prs.slides._sldIdLst)
              new_position = target_index + 1  # 插入到目标幻灯片的后面
              # 移除刚添加的新幻灯片(默认在最后)
              new_slide_id = slides.pop()
              # 插入到正确的位置
              slides.insert(new_position, new_slide_id)
              prs.slides._sldIdLst[:] = slides
          
          
          def delete_slide(prs, slide_index):
              # prs = Presentation(template_path)
              """
              删除给定索引处的幻灯片。
          
              :param prs: Presentation 对象
              :param slide_index: 要删除的幻灯片的索引(从0开始)
              """
              # 确保索引在范围内
              if slide_index < 0 or slide_index >= len(prs.slides):
                  print("无效的幻灯片索引")
                  return
              # 获取幻灯片ID列表
              xml_slides = list(prs.slides._sldIdLst)
          
              # 根据索引找到对应的幻灯片ID并移除
              slides_id_to_delete = xml_slides[slide_index]
              prs.slides._sldIdLst.remove(slides_id_to_delete)
          
              # 保存修改后的PPT
              # prs.save(modified_path)
          
          
          def copy_ppt(pages, template_path="template.pptx", source_index=1, target_index=1,
                  android     modified_path="modified_example.pptx"):
              prs = Presentation(template_path)
              copy_pages, center_pages = pages - 2, len(prs.slides) - 2
              if copy_pages != center_pages:
          
                  if copy_pages < center_pages:
                      start_page_index = center_pages
                      for _ in range(center_pages - copy_pages):
                          delete_slide(prs, start_page_index)
                          start_page_index -= 1
                  else:
                      n = (copy_pages // center_pages) * center_pages
                      m = (copy_pages // center_pages + 1) * center_pages - copy_pages
                      start_page_index = center_pages
                      for _ in range(n):
                          for i in range(1, center_pages + 1):
                              copy_slide_and_insert_after(prs, i, start_page_index)
                              start_page_index += 1
                      if m:
          
                          for _ in range(m):
                              delete_slide(prs, start_page_index)
                              start_page_index -= 1
          
              prs.save(modified_path)
          
          
          if __name__ == '__main__':
              # 使用示例
              # data=extract_ppt_with_style("template.pptx", "output_styles.json")
              #
              # prompt_text=f"""
              # # ppt json 模版
              # {data}
              # # 模版使用说明
              # - 每个 slide  的  shapes 的 结构 (元素个数)是不可变得
              # - 每个 slide  的  shapes 里面的字典的key 不可变 值是可以变 的
              # - 第一 slide  是不可被复制的  且 必须在第一个位置   但是内容 是可变的   slide_number 也是可变的
              # - 最后一个 slide 也是不可复制的 且 必须在最后一个位置  但是内容 是可变的  slide_number 也是可变的
              # - 中简的  slide  是可以被复制的 但是顺序不能改变
              #     - 例如  中间 有 两个 slide  2,3   如果你的ppt 中间需要5个 slide  那么复制 顺序是  2,3,2,3,2 复制后可以改其他slide_number 名字
              # # 明白上述模版使用要求之后  请 完成主题为:人工智能改变世界的ppt大纲 并且 使用上述模版 生成对应的json
              # """
              llm_json =[]
              # copy_ppt(len(llm_json))
              data = extract_ppt_with_style("modified_example.pptx", "output_styles.json")
          
              apply_styles_to_ppt("modified_example.pptx", "output_styles.json", "new_ppt.pptx", llm_json)
          

          到此这篇关于利用Python自动化处理PPT样式与结构的解决方案的文章就介绍到这了,更多相关Python自动化处理PPT内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

          0

          上一篇:

          下一篇:

          精彩评论

          暂无评论...
          验证码 换一张
          取 消

          最新开发

          开发排行榜