小豬的Python學習之旅 —— 22.安靜!吵到我用TNT了

coder-pig發表於2018-06-01

小豬的Python學習之旅 —— 22.安靜!吵到我用TNT了

標籤:Python


一句話概括本文

靈感來自於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 找到):

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加群交易屁眼 中的一個關鍵詞即可通過;

驗證通過後回覆 加群 即可獲得加群連結(不要把機器人玩壞了!!!)~~~
歡迎各種像我一樣的Py初學者,Py大神加入,一起愉快地交流學♂習,van♂轉py。


相關文章