執筆者 みやじ
はじめに
この記事は、OUCC Advent Calendar 2021の18日目の記事です。前回はAreiさんのハンドヘルドコンピュータの製作と展望でした。 今回はPower Pointのスライドショーを動画に変換しようとしてみました。
やり方
音声付きスライドを考えると動画を作るためには画像と音声が必要です。しかし、pptxファイルやppsxファイルから直接画像や音声を取り出すことのできるライブラリはありませんでした。
まずは音声ファイルの抽出から行うことにしました。pptxを含むoffceファイルは中身はただのzipファイルなのでos.Rename()を使ってファイル形式を変え、zipfileライブラリで開くことで中の情報に直接アクセスできます。音声ファイルがあるのは ppt/media のディレクトリで、どの音声がどのスライドに紐づけられているのかというのは ppt/slides/_rels にあるrelsファイルに書いてあります。relsファイルはxmlファイルなのでこれもos.Rename()でファイル形式を変えて扱います。そこから音声ファイルとスライドの関連情報が得られ、音声ファイルの準備は完了します。
次に画像ファイルの作成ですが、現状pptxから自力で画像を作ること大変難しいのでPower Pointアプリを起動し、画像をエクスポートさせます。これはcomtypesライブラリを用いることで実現しました。
最後にこれらをまとめるのですがこれにはffmpegというコマンドライン上から動画編集ができるソフトをいれ、python上でそのコマンド実行するというゴリ押しの方法で行いました。
作っている途中で気づいたこと
Power Pointのエクスポートに動画に出力する機能があり、自作するより圧倒的に多機能であること
それを知って
デバッグする気力がなくなったため、途中からデバッグしてません。理論は分かったけど、もう動作させる理由がありませんでした。たぶん動かないと思います。
import os
import re
import zipfile
import glob
from comtypes import client
import xml.etree.ElementTree as ET
import subprocess
def getM4aPath(relsPath):
root = ET.fromstring(relsPath)
for child in root:
data = child.attrib["Target"]
print(data)
if re.match(".+\.m4a",data):
return "pptxZipTemp/voice/ppt/" + data.replace("../","")
return ""
def generateMp4(relsPathList,pptxPath):
aviPathList = []
concat = "concat:"
for index,relsPath in enumerate(relsPathList):
voicePath = getM4aPath(relsPath)
pngPath = "pptxZipTemp/images/slide{}.PNG".format(index)
aviPathList.append("pptxZipTemp/avi/temp{}.avi".format(index))
subprocess.run("ffmpeg -loop 1 {} -i {} -vodec mpeg4 -acodec pcm_s16le -shortest {}".format(pngPath,voicePath,aviPathList[index]),shell=True)
concat = concat + aviPathList[index] + "|"
concat = re.sub(".+\|$","",concat)
pptxPath = pptxPath.split("/")
pptxPath = pptxPath[-1].split(".")
subprocess.run("ffmpeg -f concat -i {} -vcodec libx264 -acodec aac -pix_fmt yuv420p {}".format(concat,pptxPath[0]))
def zip2mp4(zipPath,pptxPath):
with zipfile.ZipFile(zipPath) as pptxZip:
inforLi = pptxZip.infolist()
voicePathList = []
relsPathList = []
for info in inforLi:
fname = info.filename
if re.match(".+\.m4a",fname):
voicePathList.append(fname)
elif re.match("ppt/slides/_rels/.+\.rels",fname):
relsPathList.append(fname)
print(fname)
for voiceIndex, voicePath in enumerate(voicePathList):
pptxZip.extract(voicePath, "pptxZipTemp/voice")
voicePathList[voiceIndex] = "pptxZipTemp/voice/" + voicePath
for relsIndex, relsPath in enumerate(relsPathList):
renamedRelsPath = relsPath.replace(".rels","")
pptxZip.extract(relsPath, "pptxZipTemp/rels/")
os.rename("pptxZipTemp/rels/"+relsPath, "pptxZipTemp/rels/"+renamedRelsPath)
relsPathList[relsIndex] = "pptxZipTemp/rels/" + renamedRelsPath
generateMp4(relsPathList,pptxPath)
def export_img(fname, odir):
application = client.CreateObject("Powerpoint.Application")
application.Visible = True
current_folder = os.getcwd()
presentation = application.Presentations.open(os.path.join(current_folder, fname))
export_path = os.path.join(current_folder, odir)
presentation.Export(export_path, FilterName="png")
presentation.close()
application.quit()
def rename_img(odir):
file_list = glob.glob(os.path.join(odir, "*.PNG"))
for fname in file_list:
new_fname = fname.replace('スライド', 'slide')
os.rename(fname, new_fname)
def getLines(path):
reg = '\.pp[s|t]x'
renamedPath = re.sub(reg,".zip", path)
print("match:")
print(renamedPath)
print(path)
if os.path.isfile(path):
try:
subprocess.run("mkdir pptxZipTemp",shell=True)
export_img(path,"pptxZipTemp/images")
rename_img("pptxZipTemp/images")
os.rename(path,renamedPath)
print("This path exists.")
print(renamedPath)
zip2mp4(renamedPath,path)
except OSError as e:
print("An OSError occured")
print(e)
finally:
os.rename(renamedPath,path)
print("終了しました")
else:
print(path+"は存在しないか、対応していません。")
def main():
while True:
print("scan:")
inputString = input()
if inputString == "exit":
break
else:
getLines(inputString)
if __name__ == "__main__":
main()