小猪的Python学习之旅 —— 22.安静!吵到我用TNT了

一句话概括本文

灵感来自于5.15锤子鸟巢发布会上老罗用闪念胶囊一键生成了32张PPT,
原理利用Python-pptx库,通过编写模板的方式自动生成批量PPT!


引言

锵锵锵,失踪人口回归,距离上一篇文章已过去14天。不是我偷懒不更新,
主要是转岗打杂了,很杂那种,每天要处理一堆和开发无关的琐事,
真的是敲码五分钟,打杂两小时...还是当个单纯的开发仔好啊。

标题没错,是第22篇不是21,不用回去翻,没看漏,第20篇写的是爬取Gank.io
接口的所有数据到MySQL,第21篇已经定好利用Flask编写一个API接口,
代码是实现了,部署还有些问题,所以还没些,先占个坑。

本节的话,是最近两天在折腾的一个东西,因为组内正在整在线早教
相关的东西,老师呢,要做很多的课件,但是大部分的内容都是重复的,
可能就图片会变下,流程可能会变一点,然后就找到我,让我想想有没有办法
自动生成减少她们的工作量,对,她们想要的就是 自动生成PPT

还记得5.15锤子鸟巢发布会吗?不记得?看到这个图你应该想起什么了~

老罗现场展示了次时代电脑:TNT工作站 ,这里就不吐槽现场演示时的
各种小失误和理解万岁了。直播回顾的视频可以到B站看:

515锤子科技鸟巢发布会全程回顾

发布会直播我是有全程看完的,有个地方吸引了我的注意,在视频里69:08处,
老罗利用闪念胶囊直接就生成了32张PPT,给人一种错觉:

卧槽,好屌,以后连PPT都不用自己做了

其实不然,如果是细心的观众基本会发现一个规律,生成的PPT都是非常简单
的PPT,一个黑色渐变的大背景,配几行字,或者再配个图片,PPT动画也没有。
来,来给你个这样的PPT生成给我看:

这种批量生成简单PPT的套路,我觉得思路无非这样:

根据情形,定义几套模板,然后约定一个规则,根据不同的内容调用
不同的模板,进行内容填充。

套路知道了,接下来就是看看Python有没有支持库了~
找到两个pptxwin32com,本节只用前者,因为后者的文档是真的
看得人头皮发麻,而且网上的例子非常少...


1.python-pptx库


官方文档http://python-pptx.readthedocs.io/en/latest/index.html
官方仓库https://github.com/scanny/python-pptx
安装库pip install python-pptx

对了,因为win32com那个库要调用微软的PowerPoint,我把电脑重装回
Win 10了,索性安装了最新版的PyCharm,然后发现创建的工程和以前
创建的工程结构不一样,多了个这样的东西:

终端运行也变成了:(venv) F:\Python> 这样,这个就是虚拟环境
简单点说就是对开发环境进行隔离,比如你这个项目基于Python 2.x,另一个项目
基于Python 3.x,或者说着两个项目里依赖的同一个模块用着不同的版本,通过
虚拟环境可以让这两个项目互不干扰,将所需的包安装到独立的环境中。

Python中常用的创建和管理虚拟环境的工具有:virtualenvpyvenv
Pycharm默认带有virtualenv,新建的时候就可以看到,具体怎么定制化,
自行百度吧~对了,下载模块都在Lib/site-packages目录下!


2.实现流程分析

首先的话,先想想有哪些模板,罗列下:

  • 1.只有一张图片
  • 2.只有一条文字
  • 3.一条文字和一张图片
  • 4.两条文字
  • 5.四条文字

然后布局大概这样咯:

接着定义一个规则,数据怎么传,这里采用最简单的套路,写个txt文件,
每行代表一个PPT,参数通过逗号间隔,于是完整的PPT对应这样的txt文件:

通过逗号分割参数,优先判断是否有图片,有的话走模板1,3,
其他再另外判断。好的,思路有了,接下来开始一步步实现吧。


3.代码实现

PS:这里不去介绍怎么用,看不懂自己去翻文档,我也是自己摸索,
有我写的示例参考,应该觉得很欣慰了。


1.定义一个厘米转英寸的方法

以为PPT里的位置和大小用到的单位都是厘米,需要转换下

# 厘米转英寸
def cm_to_in(cm):
    return Inches(cm / 2.54)

2.编写模板

先是模板1,传入Presentation的对象,这个你可以理解成PPT对象,
调用该对象的.slides.add_slide()方法添加一张幻灯片,pptx库为我们
提供了八个不一样的模板,喜欢的可以自己一个个试,这里我们直接用第七
空白幻灯片,下标从0开始,所以是prs.slide_layouts[6],幻灯片
加了之后,调用Presentation的save(ppt文件名)函数打开生成的PPT,
然后点击设置 -> 幻灯片大小 -> 直接选择16:9或者设置幻灯片大小,比如我
的,这里的宽度和高度就是我们幻灯片的大小了,后面填充满屏的图片就要
用到这个。

然后调用add_picture函数添加一个满屏图片:

slide.shapes.add_picture(ppt_bg_path, cm_to_in(0), cm_to_in(0), cm_to_in(25.4), cm_to_in(14.288))

然后模板1就写完了,完成代码如下:

# 模板1:只有一张图片
def model_1(prs, pic_path):
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    slide.shapes.add_picture(pic_path, cm_to_in(0), cm_to_in(0), cm_to_in(25.4), cm_to_in(14.288))
    
# 调用:
presentation = Presentation(ppt_file_name)
model_1(presentation, laoluo_bg_path)
    

打开生成的ppt:

哟,成功生成,接着到模板2:

填充满屏图片,然后新建一个文本框:

title_box = slide.shapes.add_textbox(cm_to_in(3.89), cm_to_in(5.35), cm_to_in(17.61), cm_to_in(3.59))

再接着添加一个文本域:

paragraph = title_box.text_frame.add_paragraph()

然后就可以对文本域里进行文字相关的操作了:

    paragraph.text = title  # 设置文本
    paragraph.vertical_anchor = MSO_VERTICAL_ANCHOR.MIDDLE  # 设置垂直方向对齐方式
    paragraph.alignment = PP_PARAGRAPH_ALIGNMENT.CENTER # 设置水平方向对齐方式
    paragraph.font.size = Pt(60)    # 设置文本大小,PT代表磅
    paragraph.font.name = '微软雅黑'    # 设置字体
    paragraph.font.color.rgb = RGBColor(255, 255, 255)  # 设置字体颜色

参数传递下,调用这个模板2,查看下生成的效果:

接下来如法炮制剩下的三个模板,主要是难点是控件的位置和宽高设置
有两种操作:

最简单的操作:

随便填个坐标和宽高,运行后打开生成的PPT,自行调整
位置,然后记录下何时的位置和宽高,然后改代码。

复杂点的操作:

自行计算,比如模板5,三个小标题,先减去左右的间隔,
然后三等分,循环动态计算小标题的起始位置。


3.配置文件读取

接下来编写一个读取文件内容,调用对应方法的函数,代码如下

# 读取配置文件调用模板的方法
def read_rules(prs, filename):
    if os.path.exists(filename):
        with open(filename, 'r+', encoding='utf-8') as f:
            for rule in f:
                word_list = rule.replace('\n', '').split(',')
                if 'png' in rule or 'jpg' in rule:
                    if len(word_list) == 1:
                        model_1(prs, os.path.join(c.res_pictures, word_list[0]))
                    else:
                        model_3(prs, word_list[0], os.path.join(c.res_pictures, word_list[1]))
                else:
                    if len(word_list) == 1:
                        model_2(prs, word_list[0])
                    elif len(word_list) == 2:
                        model_4(prs, word_list[0], word_list[1])
                    elif len(word_list) == 4:
                        model_5(prs, word_list[0], word_list[1], word_list[2], word_list[3])

4.代码执行

调用配置文件读取的函数

if __name__ == '__main__':
    t.is_dir_existed(c.outputs_documents_path)
    ppt_existed(ppt_file_name)
    presentation = Presentation(ppt_file_name)
    read_rules(presentation, rules_path)
    presentation.save(ppt_file_name)

运行结果


4.有些东西要说说

批量生成是挺爽的,不过呢,不支持直接生成动画哦!!!
然后这种简单的PPT,其实粘贴复制修改图片的效率可能比起你一个个
模板编写快一些...不得不说有些鸡肋,哦,对哦,这个还支持生成图表,
怎么整可以自行查阅官方文档。

另外的win32com的库,貌似功能更加强大,可能支持动画吧,但是文档是
真的难啃,就没有深入去研究了,有兴趣可以自己去折腾折腾。附上用
win32com这个库时遇到的问题的解决方法:

  • 1.Python3安装win32com
pip install pypiwin32
  • 2.哪里有win32com的文档

微软官网有,不过看到脑壳痛,介绍个工具:oleview,网上一搜一堆
不过这个用的时候会遇到一个问题,这里顺带记录下笔者win10电脑遇到的
一些情况:

IVIEWERS.DLL缺失:

网上搜下这个dll文件,下载完把文件复制到Windows/system32
然后管理员模式打开命令提示符,键入:regsvr32 iviewers.dll
注册这个dll运行库就可以了。不过呢,win10 64位这样的执行是会报错的:

模块iviewers.dll可能与您正在运行的Windows版本不兼容,检查该模块是否与
regsvr.exe的x86或x64版本兼容

你要做的是把dll文件拷贝到Windows/SysWOW64,然后管理员模式打开命令提示符
cd到这个目录下,接着执行regsvr32 iviewers.dll即可。

如果出现:对DllRegisterServer的调用失败,错误代码为0x80070005
那是UAC的缘故,你没有以管理员模式打开命令提示符

如果解决了,应该能正常打开,左侧招到PowerPoint那项,双击打开,
然后呢,这个工具不支持查找,你可以把内容全选然后复制到如Sublime Text
这样的代码查看工具上,ctrl + f 查找关键字,然后一步步定位出实现某个
功能需要用到的一些函数。


小结

本节讲解了一波利用Python-pptx批量生成N张PPT的套路,不禁再一次感叹
人生苦短,我用Python,最后祝六一儿童节快乐~


参考文献


附:最终代码(都可以在:https://github.com/coder-pig/ReptileSomething 找到):

image
import pptx
import config as c
import tools as t
from pptx import Presentation
from pptx.dml.color import RGBColor
from pptx.util import Inches, Pt
from pptx.enum.text import MSO_VERTICAL_ANCHOR, PP_PARAGRAPH_ALIGNMENT
import os

rules_path = os.path.join(c.res_documents, 'ppt_rules.txt')
ppt_bg_path = os.path.join(c.res_pictures, 'ppt_bg.png')
laoluo_bg_path = os.path.join(c.res_pictures, 'laoluo.jpg')
last_bg_path = os.path.join(c.res_pictures, 'last.png')
story_bg_path = os.path.join(c.res_pictures, 'story.png')
ppt_file_name = os.path.join(c.outputs_documents_path, 'result.pptx')


# 厘米转英寸
def cm_to_in(cm):
    return Inches(cm / 2.54)


# 判断课件是否存在,不存在的新建一个空白
def ppt_existed(ppt_name):
    if not os.path.exists(ppt_name):
        prs = Presentation()
        prs.slide_height = cm_to_in(14.35)
        prs.slide_width = cm_to_in(25.5)
        prs.save(ppt_name)


# 模板1:只有一张图片
def model_1(prs, pic_path):
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    slide.shapes.add_picture(pic_path, cm_to_in(0), cm_to_in(0), cm_to_in(25.4), cm_to_in(14.288))


# 模板2:只有一个标题
def model_2(prs, title):
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    slide.shapes.add_picture(ppt_bg_path, cm_to_in(0), cm_to_in(0), cm_to_in(25.4), cm_to_in(14.288))
    title_box = slide.shapes.add_textbox(cm_to_in(3.89), cm_to_in(5.35), cm_to_in(17.61), cm_to_in(3.59))
    paragraph = title_box.text_frame.add_paragraph()
    paragraph.text = title
    paragraph.vertical_anchor = MSO_VERTICAL_ANCHOR.MIDDLE
    paragraph.alignment = PP_PARAGRAPH_ALIGNMENT.CENTER
    paragraph.font.size = Pt(60)
    paragraph.font.name = '微软雅黑'
    paragraph.font.color.rgb = RGBColor(255, 255, 255)


#  模板3:有字,有图片
def model_3(prs, title, pic_path):
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    slide.shapes.add_picture(ppt_bg_path, cm_to_in(0), cm_to_in(0), cm_to_in(25.4), cm_to_in(14.288))
    img = slide.shapes.add_picture(pic_path, cm_to_in(0), cm_to_in(0), height=cm_to_in(11.72))
    img.left = int(prs.slide_width / 2 + (prs.slide_width / 2 - img.width) / 2)
    img.top = int((prs.slide_height - img.height) / 2)
    title_box = slide.shapes.add_textbox(cm_to_in(2), cm_to_in(5.35), int(prs.slide_width / 3), cm_to_in(3.59))
    paragraph = title_box.text_frame.add_paragraph()
    paragraph.text = title
    paragraph.font.size = Pt(44)
    paragraph.font.name = '微软雅黑'
    paragraph.font.color.rgb = RGBColor(255, 255, 255)


# 模板4:两行文字,一大一小
def model_4(prs, title, content):
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    slide.shapes.add_picture(ppt_bg_path, cm_to_in(0), cm_to_in(0), cm_to_in(25.4), cm_to_in(14.288))
    # 一级标题
    title_box_1 = slide.shapes.add_textbox(cm_to_in(1.27), cm_to_in(2.04), cm_to_in(22.86), cm_to_in(3.18))
    paragraph_1 = title_box_1.text_frame.add_paragraph()
    paragraph_1.text = title
    paragraph_1.vertical_anchor = MSO_VERTICAL_ANCHOR.MIDDLE
    paragraph_1.alignment = PP_PARAGRAPH_ALIGNMENT.CENTER
    paragraph_1.font.size = Pt(44)
    paragraph_1.font.name = '微软雅黑'
    paragraph_1.font.color.rgb = RGBColor(255, 255, 255)
    # 二级标题
    title_box_2 = slide.shapes.add_textbox(cm_to_in(7.46), cm_to_in(6.4), cm_to_in(10.47), cm_to_in(2.39))
    paragraph_2 = title_box_2.text_frame.add_paragraph()
    paragraph_2.text = title
    paragraph_2.vertical_anchor = MSO_VERTICAL_ANCHOR.MIDDLE
    paragraph_2.alignment = PP_PARAGRAPH_ALIGNMENT.CENTER
    paragraph_2.font.size = Pt(32)
    paragraph_2.font.name = '微软雅黑'
    paragraph_2.font.color.rgb = RGBColor(255, 255, 255)


# 模板5:一行文字,多个小标题
def model_5(prs, title, *content):
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    slide.shapes.add_picture(ppt_bg_path, cm_to_in(0), cm_to_in(0), cm_to_in(25.4), cm_to_in(14.288))
    title_box_1 = slide.shapes.add_textbox(cm_to_in(1.27), cm_to_in(2.04), cm_to_in(22.86), cm_to_in(3.18))
    paragraph_1 = title_box_1.text_frame.add_paragraph()
    paragraph_1.text = title
    paragraph_1.vertical_anchor = MSO_VERTICAL_ANCHOR.MIDDLE
    paragraph_1.alignment = PP_PARAGRAPH_ALIGNMENT.CENTER
    paragraph_1.font.size = Pt(44)
    paragraph_1.font.name = '微软雅黑'
    paragraph_1.font.color.rgb = RGBColor(255, 255, 255)
    # 动态构建小标题
    module_width = (prs.slide_width - cm_to_in(1.27) * 2) / len(content)
    for i in range(0, 3):
        title_box = slide.shapes.add_textbox(cm_to_in(1.27) + i * module_width, cm_to_in(6.4), module_width,
                                             cm_to_in(2.39))
        paragraph = title_box.text_frame.add_paragraph()
        paragraph.text = content[i]
        paragraph.vertical_anchor = MSO_VERTICAL_ANCHOR.MIDDLE
        paragraph.alignment = PP_PARAGRAPH_ALIGNMENT.CENTER
        paragraph.font.size = Pt(32)
        paragraph.font.name = '微软雅黑'
        paragraph.font.color.rgb = RGBColor(255, 255, 255)


# 读取配置文件调用模板的方法
def read_rules(prs, filename):
    if os.path.exists(filename):
        with open(filename, 'r+', encoding='utf-8') as f:
            for rule in f:
                word_list = rule.replace('\n', '').split(',')
                if 'png' in rule or 'jpg' in rule:
                    if len(word_list) == 1:
                        model_1(prs, os.path.join(c.res_pictures, word_list[0]))
                    else:
                        model_3(prs, word_list[0], os.path.join(c.res_pictures, word_list[1]))
                else:
                    if len(word_list) == 1:
                        model_2(prs, word_list[0])
                    elif len(word_list) == 2:
                        model_4(prs, word_list[0], word_list[1])
                    elif len(word_list) == 4:
                        model_5(prs, word_list[0], word_list[1], word_list[2], word_list[3])


if __name__ == '__main__':
    t.is_dir_existed(c.outputs_documents_path)
    ppt_existed(ppt_file_name)
    presentation = Presentation(ppt_file_name)
    read_rules(presentation, rules_path)
    presentation.save(ppt_file_name)

来啊,Py交易啊

想加群一起学习Py的可以加下,智障机器人小Pig,验证信息里包含:
PythonpythonpyPy加群交易屁眼 中的一个关键词即可通过;

image

验证通过后回复 加群 即可获得加群链接(不要把机器人玩坏了!!!)~~~
欢迎各种像我一样的Py初学者,Py大神加入,一起愉快地交流学♂习,van♂转py。


推荐阅读更多精彩内容