近期有一个杂活,需要为活动设计胸牌,需要基于PPT模板,对胸牌中的人名和职务进行替换。为了不想一个个手动操作,想通过python来实现自动化处理。最后发现用的时间比手动操作还要久……
胸牌大致样式如下,是PPT中文本框和图片的组合,需要根据数据,对身份和人名进行替换。
基于python PPTX库的操作
由于拿到的是一个.pptx格式,且只需要对其中一个组件进行替换,因此首先想直接使用python基于PPT进行操作。经过浏览,发现PPTX库是一个比较常用的库。在阅读完Python自动化操作PPT看这一篇就够了等相关资料后,发现操作起来难度并不大,于是开始实现。
为了简化操作,我PPT中的其它组件进行组合,只留下需要修改的文本框单独留下。在pptx中,组合的图形的类型为pptx.shapes.group.GroupShape,不包含文本框。所以在遍历组件时通过判断有无文本框即可选中需要进行修改的文本框,然后将文本框中内容进行修改。更多有关pptx的操作可参见python-pptx官方文档。
避坑,需要非常注意各个对象的类型末尾有无s,比如slide和slides,groupshape和groupshapes是不一样的。
from pptx import Presentation
from pptx.enum.text import PP_PARAGRAPH_ALIGNMENT
from pptx.util import Pt
from pptx.dml.color import RGBColor
ppt = Presentation("运动会.pptx")
firSlide = ppt.slides[0]
shapes = firSlide.shapes
for shape in shapes:
if shape.has_text_frame: # 提取文本框并进行修改
print(shape)
tf = shape.text_frame
tf.paragraphs[0].text = "运动员:厉齐马"
tf.paragraphs[0].alignment = PP_PARAGRAPH_ALIGNMENT.CENTER # 对齐方式
tf.paragraphs[0].font.name = '楷体' # 字体名称
tf.paragraphs[0].font.color.rgb = RGBColor(0, 0, 0) # 字体颜色
tf.paragraphs[0].font.size = Pt(28) # 字体大小
else:
print(shape)
ppt.save("运动会2.pptx")
# 输出为
# <pptx.shapes.group.GroupShape object at 0x000001C146525750> GroupShape,组合
# <pptx.shapes.autoshape.Shape object at 0x000001C146525850> Shape,为文本框
在经过上述操作后,完成了修改。得到了新的胸牌,可行性得到了初步验证,于是想通过遍历方式将所有需要调整的内容进行调整,代码如下。
此处有较多bug,由于时间紧迫,我也没有仔细去解决。其一pptx中相关的shape都是引用,拷贝时需要注意。其二是使用copy.deepCopy没法得到背景图片。还望有大佬指导一下。
from pptx import Presentation
from pptx.enum.text import PP_PARAGRAPH_ALIGNMENT
from pptx.util import Pt
from pptx.dml.color import RGBColor
import copy
inforList = ["运动员:厉齐马", "裁判员:杜苏芮"]
ppt = Presentation("运动会.pptx")
firSlide = ppt.slides[0]
shapes = firSlide.shapes
for infor in inforList:
slide = ppt.slides.add_slide(firSlide.slide_layout)
copyShape = copy.deepcopy(shapes)
shapeList = []
for shape in copyShape:
if shape.has_text_frame: # 提取文本框并进行修改
tf = shape.text_frame
tf.paragraphs[0].text = infor
tf.paragraphs[0].alignment = PP_PARAGRAPH_ALIGNMENT.CENTER # 对齐方式
tf.paragraphs[0].font.name = '楷体' # 字体名称
tf.paragraphs[0].font.color.rgb = RGBColor(0, 0, 0) # 字体颜色
tf.paragraphs[0].font.size = Pt(28) # 字体大小
shapeList.append(shape)
groundShapes = shapeList[0].shapes
groundShapes.add_group_shape(shapeList[1:])
slideShapes = slide.shapes
slideShapes.add_group_shape([groundShapes.parent])
ppt.save("运动会2.pptx")
在报错后,我发现更致命的一点是,pptx库中没有提供将组合保存为图片的相关接口,整个文档中与save有关的只有保存pptx的唯一接口save(file)。
在这两个问题的重叠下,我选择为对于每个需要填写的信息,都从原始pptx文件中进行读取原始数据,再使用python代码进行修改,然后另存为一个新的pptx文件,最后将新的pptx文件转为图片格式的方式,完成转换。这里需要注意的是,为了让最后pptx转出来的图片为我们想要的大小,需要将pptx文件中幻灯片的大小调整为图片大小。
在将ppt转换为图片的最后一部,我尝试了多种方法,包括pptx-interface(需要下载comtypes库,无法设置分辨率,清晰度欠佳),win32com(无法设置分辨率,清晰度欠佳),spire(png,jpg等位图格式可以设置分辨率,但导出png有水印,导出svg可以通过代码定向去掉水印。但是麻烦的地方在于spire的相关函数命名与pptx一样,放在一个文件内容易冲突)。
from pptx import Presentation
from pptx.enum.text import PP_PARAGRAPH_ALIGNMENT
from pptx.util import Pt
from pptx.dml.color import RGBColor
import win32com.client as win32
inforList = ["运动员:厉齐马", "裁判员:杜苏芮"]
for infor in inforList:
ppt = Presentation("运动会.pptx")
firSlide = ppt.slides[0]
shapes = firSlide.shapes
shapeList = []
for shape in shapes:
if shape.has_text_frame: # 提取文本框并进行修改
tf = shape.text_frame
tf.paragraphs[0].text = infor
tf.paragraphs[0].alignment = PP_PARAGRAPH_ALIGNMENT.CENTER # 对齐方式
tf.paragraphs[0].font.name = '楷体' # 字体名称
tf.paragraphs[0].font.color.rgb = RGBColor(0, 0, 0) # 字体颜色
tf.paragraphs[0].font.size = Pt(28) # 字体大小
groundShapes = shapeList[0].shapes
groundShapes.add_group_shape(shapeList[1:])
ppt.save("运动会2.pptx")
name = infor[infor.find(":") + 1:]
fullName = r"fullpath\to\workingDir\运动会2.pptx"
saveName = r"fullpath\to\workingDir\fig\\" + str(name)
# pptx-interface
utils.save_pptx_as_png(png_folder, pptfile, overwrite_folder=True)
# win32com
pptClient = win32.Dispatch('PowerPoint.Application')
pptClient.Visible = 1 # 设置为0表示后台运行,不显示,1则显示
ppt = pptClient.Presentations.Open(fullName)
ppt.SaveAs(saveName, 21) # 21为tif格式,18为png格式,17为jpg格式
pptClient.Quit()
'''
from spire.presentation.common import *
from spire.presentation import *
# 创建一个Presentation对象
presentation = Presentation()
# # 从文件加载名为"输入文档.pptx"的演示文稿数据
presentation.LoadFromFile("运动会2.pptx")
# 启用IsNoteRetained属性以在将演示文稿转换为SVG文件时保留备注内容
presentation.IsNoteRetained = True
# 遍历演示文稿中的每个幻灯片
for i, slide in enumerate(presentation.Slides):
# 构建输出文件名,格式为"SVG/ToSVG_序号.svg"
fileName = "SVG/ToSVG_" + str(i) + ".svg"
# 将当前幻灯片保存为SVG流
svgStream = slide.SaveToSVG()
# 将SVG流保存到指定文件名
svgStream.Save(fileName)
for i, slide in enumerate(presentation.Slides):
# 构建输出文件名,格式为"Output/ToImage_序号.png"
fileName = "SVG/ToImage_" + str(i) + ".png"
# 将每个幻灯片保存为大小为700 * 400像素的PNG图像
image = slide.SaveAsImageByWH(770, 1125)
# 将图像保存到指定文件名
image.Save(fileName)
# 释放图像资源
image.Dispose()
# 释放演示文稿资源
presentation.Dispose()
'''
基于python PIL库的操作
除了基于pptx的方法,其实还可以通过将模板中的背景图提取出来,然后使用PIL等工具,加入文本框,实现图片的批量生成。但是基于PIL的方法需要对文本框的位置进行计算和估计,才能确保居中等格式,可参见PIL文本框居中,因此对于不规则的文本,可能会出现排版的问题。
PIL的字体设置,可以去c/windows/fonts中复制相应的ttf文件到相应工作路径
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
inforList = ["运动员:厉齐马", "裁判员:杜苏芮", "志愿者:天歌"]
font = ImageFont.truetype("simkai.ttf", 50, encoding="unic")
for infor in inforList:
img = Image.open("back.png")
width, height = img.size
draw = ImageDraw.Draw(img)
w = draw.textlength(infor, font=font)
draw.text(((width - w) / 2, 350), infor, fill=(0, 0, 0), font=font)
name = infor[infor.find(":") + 1:]
saveName = "./fig/" + name + ".png"
img.save(saveName)