利用Python自動化處理PPT樣式與結(jié)構(gòu)的解決方案
引言
PowerPoint(PPT)是職場中常用的辦公工具,但手動設(shè)計(jì)和調(diào)整樣式往往耗時耗力。本文將介紹一套基于Python的自動化解決方案,通過代碼實(shí)現(xiàn)以下功能:
- 提取PPT樣式:將PPT的文本格式、顏色、布局等信息保存為JSON文件。
- 應(yīng)用樣式到模板:根據(jù)JSON定義的樣式生成新PPT。
- 幻燈片增刪與復(fù)制:靈活調(diào)整PPT結(jié)構(gòu),滿足動態(tài)內(nèi)容需求。
代碼功能概述
核心功能模塊
樣式提取與保存
- 函數(shù):
extract_ppt_with_style(ppt_path, output_json) - 功能:遍歷PPT的每一頁,提取文本框的字體、顏色、段落對齊方式等樣式信息,并保存為結(jié)構(gòu)化的JSON文件。
- 函數(shù):
樣式應(yīng)用與生成
- 函數(shù):
apply_styles_to_ppt(template_path, json_path, output_pptx, data_json_llm) - 功能:根據(jù)JSON定義的樣式,將文本內(nèi)容和格式應(yīng)用到指定模板,生成符合要求的新PPT。
- 函數(shù):
幻燈片結(jié)構(gòu)管理
- 函數(shù):
copy_slide_and_insert_after、delete_slide、copy_ppt - 功能:復(fù)制、刪除幻燈片,并根據(jù)需求動態(tài)調(diào)整中間頁的數(shù)量(如擴(kuò)展或壓縮內(nèi)容頁)。
- 函數(shù):
代碼詳解
1. 提取PPT樣式(extract_ppt_with_style)
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),
"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
- 關(guān)鍵點(diǎn):
- 樣式解析:記錄字體名稱、大小、粗體、斜體、顏色(主題色或RGB值)。
- 結(jié)構(gòu)化存儲:JSON中每一頁(
slide)包含多個形狀(shapes),每個形狀包含段落(paragraphs)和文本片段(runs)。 - 壓縮優(yōu)化:
json_compress函數(shù)簡化冗余數(shù)據(jù)(如通用形狀名稱),提升存儲效率。
2. 應(yīng)用樣式生成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):
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]
# 應(yīng)用文本內(nèi)容
run.text = data_json_llm[slide_idx]["shapes"].pop()["paragraphs"]
# 應(yīng)用樣式
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)
- 關(guān)鍵點(diǎn):
- 樣式復(fù)用:從JSON中讀取字體、顏色等信息,直接應(yīng)用到模板PPT的對應(yīng)位置。
- 動態(tài)內(nèi)容替換:通過
data_json_llm參數(shù),可結(jié)合LLM(如GPT)生成的文本內(nèi)容動態(tài)填充PPT。
3. 幻燈片結(jié)構(gòu)管理
3.1 復(fù)制與插入幻燈片
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)
# 復(fù)制形狀和關(guān)系
for shape in source_slide.shapes:
new_el = deepcopy(shape.element)
new_slide.shapes._spTree.insert_element_before(new_el, 'p:extLst')
# 調(diào)整位置
slides = list(prs.slides._sldIdLst)
new_slide_id = slides.pop()
slides.insert(target_index + 1, new_slide_id)
prs.slides._sldIdLst[:] = slides
- 功能:復(fù)制指定幻燈片并插入到目標(biāo)位置后,保持布局和元素一致性。
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實(shí)現(xiàn)刪除,避免直接操作可能導(dǎo)致的格式錯誤。
3.3 動態(tài)擴(kuò)展/壓縮PPT頁數(shù)
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:
# 復(fù)制中間頁
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)
- 應(yīng)用場景:根據(jù)需求動態(tài)調(diào)整中間頁的數(shù)量(如擴(kuò)展到5頁或壓縮到3頁),保持首尾頁固定。
使用示例
場景1:生成符合樣式的PPT
# 1. 提取原始PPT的樣式
extract_ppt_with_style("template.pptx", "output_styles.json")
# 2. 生成新內(nèi)容(例如通過LLM)
llm_json = [...] # LLM生成的文本內(nèi)容
# 3. 應(yīng)用樣式生成最終PPT
apply_styles_to_ppt("template.pptx", "output_styles.json", "new_ppt.pptx", llm_json)
場景2:動態(tài)調(diào)整PPT頁數(shù)
# 假設(shè)原始模板有5頁(首尾固定,中間3頁) copy_ppt(pages=7, template_path="template.pptx") # 最終生成7頁:1(首)+5(中間復(fù)制)+1(尾)
應(yīng)用場景
- 企業(yè)報告自動化:根據(jù)數(shù)據(jù)動態(tài)生成季度報告,保持統(tǒng)一格式。
- 培訓(xùn)材料生成:批量創(chuàng)建多套PPT,僅需調(diào)整中間內(nèi)容頁。
- 營銷素材管理:快速復(fù)制產(chǎn)品介紹模板,替換文本和樣式。
總結(jié)
本文提供的代碼庫實(shí)現(xiàn)了從PPT樣式提取、動態(tài)內(nèi)容生成到結(jié)構(gòu)管理的全流程自動化。開發(fā)者可通過以下方式進(jìn)一步優(yōu)化:
- 集成LLM:將文本生成部分與GPT等模型結(jié)合,實(shí)現(xiàn)從內(nèi)容到樣式的全自動化。
- 圖形處理:擴(kuò)展對圖片、圖表樣式的解析與應(yīng)用。
- 用戶界面:封裝為GUI工具,降低使用門檻。
通過這種方式,企業(yè)可大幅減少PPT制作時間,專注于內(nèi)容創(chuàng)新而非格式調(diào)整。
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": 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_data)
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"]
# 假設(shè) 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":
# 解析十六進(jìn)制顏色
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:
# 默認(rèn)顏色(黑色)
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):
"""
復(fù)制源幻燈片并將其插入到目標(biāo)幻燈片的后面。
:param prs: Presentation 對象
:param source_index: 源幻燈片的索引(從0開始)
:param target_index: 目標(biāo)幻燈片的索引(新幻燈片將插入到其后面)
"""
# 獲取源幻燈片
source_slide = prs.slides[source_index]
# 創(chuàng)建新幻燈片(使用相同的布局)
new_slide_layout = source_slide.slide_layout
new_slide = prs.slides.add_slide(new_slide_layout)
# 復(fù)制所有形狀(包括文本框、圖片、圖表等)
for shape in source_slide.shapes:
el = shape.element
new_el = deepcopy(el)
new_slide.shapes._spTree.insert_element_before(new_el, 'p:extLst')
# 復(fù)制關(guān)系(如超鏈接、注釋等)
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
)
# 調(diào)整幻燈片順序:將新幻燈片移動到目標(biāo)位置的后面
slides = list(prs.slides._sldIdLst)
new_position = target_index + 1 # 插入到目標(biāo)幻燈片的后面
# 移除剛添加的新幻燈片(默認(rèn)在最后)
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開始)
"""
# 確保索引在范圍內(nèi)
if slide_index < 0 or slide_index >= len(prs.slides):
print("無效的幻燈片索引")
return
# 獲取幻燈片ID列表
xml_slides = list(prs.slides._sldIdLst)
# 根據(jù)索引找到對應(yīng)的幻燈片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,
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 的 結(jié)構(gòu) (元素個數(shù))是不可變得
# - 每個 slide 的 shapes 里面的字典的key 不可變 值是可以變 的
# - 第一 slide 是不可被復(fù)制的 且 必須在第一個位置 但是內(nèi)容 是可變的 slide_number 也是可變的
# - 最后一個 slide 也是不可復(fù)制的 且 必須在最后一個位置 但是內(nèi)容 是可變的 slide_number 也是可變的
# - 中簡的 slide 是可以被復(fù)制的 但是順序不能改變
# - 例如 中間 有 兩個 slide 2,3 如果你的ppt 中間需要5個 slide 那么復(fù)制 順序是 2,3,2,3,2 復(fù)制后可以改其他slide_number 名字
# # 明白上述模版使用要求之后 請 完成主題為:人工智能改變世界的ppt大綱 并且 使用上述模版 生成對應(yīng)的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)
到此這篇關(guān)于利用Python自動化處理PPT樣式與結(jié)構(gòu)的解決方案的文章就介紹到這了,更多相關(guān)Python自動化處理PPT內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python處理yaml和嵌套數(shù)據(jù)結(jié)構(gòu)技巧示例
這篇文章主要為大家介紹了Python處理yaml和嵌套數(shù)據(jù)結(jié)構(gòu)技巧示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
如何利用Python動態(tài)模擬太陽系運(yùn)轉(zhuǎn)
這篇文章主要給大家介紹了關(guān)于如何利用Python動態(tài)模擬太陽系運(yùn)轉(zhuǎn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
python列表排序用?sort()和sorted()的區(qū)別
這篇文章主要介紹了python列表排序用?sort()和sorted()的區(qū)別,主要比較?Python?中用于列表排序的兩種函數(shù)?sort()?和?sorted(),選擇合適的排序函數(shù),下文詳細(xì)內(nèi)容需要的小伙伴可以參考一下2022-03-03
Flask框架的學(xué)習(xí)指南之制作簡單blog系統(tǒng)
本文是Flask框架的學(xué)習(xí)指南系列文章的第二篇主要給大家講述制作一個簡單的小項(xiàng)目blog系統(tǒng)的過程,有需要的小伙伴可以參考下2016-11-11
解決pyinstaller打包exe文件出現(xiàn)命令窗口一閃而過的問題
今天小編就為大家分享一篇解決pyinstaller打包exe文件出現(xiàn)命令窗口一閃而過的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-10-10
Python+Tinify實(shí)現(xiàn)高效批量壓縮圖片
這篇文章主要為大家詳細(xì)介紹了Python如何利用Tinify實(shí)現(xiàn)高效批量壓縮圖片功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2025-02-02
基于Python實(shí)現(xiàn)一個高效本地文件智能搜索工具
在日常工作中,我們經(jīng)常需要在本地磁盤中搜索特定文件,本文將基于Python開發(fā)一款輕量高效的文件智能搜索工具,文中的示例代碼講解詳細(xì),需要的可以了解下2025-12-12

