PPTファイルに音声を自動で埋め込む その2 Pythonによる実装の準備

プログラミング

AHKにより実装したPPT(Powerpoint)ファイルへの音声埋め込みは,処理速度が遅く,しかも動作中はPCを使えなくなってしまいます。PPTのGUI操作をAHKに実行させているからです。これでは,大量のPPTファイルを処理したい用途では使い物になりません。

そこで,Pythonを使ってPPTファイルに直接音声データを埋め込むことを考えました。現在,フリーのPythonモジュールだけ使って音声データ埋め込みができることを確認できています。目標としている「クリックで起動されるアニメーションに音声データを挿入する」ところまではもう少しというところまで漕ぎつけました。この段階でわかったことを,私が理解した範囲で紹介します。

<注意>

この一連の投稿で紹介する技術情報のほとんどは公式のデータを参照にしたものではなく「ヤじるし」の憶測に基づいたものです。信頼性には問題があります。間違っている部分も多いと思います。「・・・です。」と断言している部分があっても,必ず「と,考えられる。」を付けて読んでください。

また,投稿した技術情報やコードに基づいて作られたソフトウェアが,貴重なデータの損失につながる可能性もあります。現在試作しているコードも,お手本になるとはとても呼べない出来です。公開したとしても,参考にする場合は慎重にお願いします。

この投稿は書きかけです。修正や追加をしていきます。

<閉じる>

基本的な考え方

これから紹介する方法は,PPTの標準的なフォーマットであるpptx形式(Windowsの拡張子が“.pptx”)のデータのみを対象としています。“.ppt”などのフォーマットのものは,pptx形式に変換してからでないと対応できないでしょう。

処理の流れは,以下のようになります。

  • pptxファイルをzip形式で展開
    pptxファイル(仮にファイル名をfilename.pptxとする)は,複数のフォルダに格納されたファイルをzip圧縮したものになっている。これをPython標準のモジュールで展開する。展開されたフォルダやファイルは,filenameという名称のフォルダ(ディレクトリ)の下に格納されている。
  • 展開されたファイルに音声データ付加の処理を施す
    スライドの切替えやクリックで起動されるアニメーションに音声データを付ける記述を,filename/ppt/slidesの下のXML(Extended Markup Language)形式のテキストファイルに書き込む。またXMLの記述と実際の音声データを関連づけているテキストファイル(.rels)にも書き加える。埋め込む音声データのファイルをリネームしてsilename/ppt/mediaの下に格納する。
  • 処理されたファイルを再度zip圧縮する
    フォルダfilenameをzip圧縮しファイル名をfilename.zipから新しい名前new_filename.pptxにリネームする。(あるいは,元のfilename.pptxを削除して,同じ名前のpptxファイルで置き換える。)

.xmlや.relsファイルに対する処理は文字列の操作のみになります。あとは,wav形式の音声データのファイルをリネームして展開されたフォルダの下にコピーするだけです。これらの処理は,pptxファイルをzip展開しないままでもできそうで,そうした方がスマートだったかもしれません。しかし,ファイルの書き直しや追加などの際の制約が大きそうだったので,一旦,展開して処理をすることにしました。

ですから処理そのものに技術的に難しい課題はありません。「xmlファイルやrelsファイルをどう書き直すか」というルールがわかってしまえば・・・です。問題は,そのルールに関する情報が,どこを調べても見つからなかったことです。(単に調べ方が下手なだけかもしれませんが。)

<注意:Windowsでのzip展開・圧縮について>

zip展開と再圧縮をWindows11のOSの操作で実行する場合は,以下のようになります。

展開:
ⅰ)filename.pptxの拡張子をzipに変更
ⅱ)zipファイルを右クリックし,「すべて展開」
 filenameという名前のフォルダができる。

再圧縮:
ⅰ)filenameフォルダを選択し右クリック > ZIPファイルに圧縮
ⅱ)filename.zipの拡張子をpptxに変更

ただし,現時点の私のシステムでは,展開したファイルを何もしないで再圧縮して得られるpptxファイルをダブルクリックしてPPTを起動すると,図のようなメッセージが出されます。

「修復」をクリックするとファイルが開かれますが,GUIでアニメーションの効果を確認すると,展開・再圧縮の前にはリンクされていたオーディオデータの情報が消えてしまっています。

少なくても一回は,再圧縮したファイルを正常に開けたという記憶があります。Windowsの更新などの影響かもしれません。zip展開と再圧縮を使う方法では音声データの付加はできないかも・・・と一瞬思いました。

そこで,Pythonでzip展開と圧縮用のコードを作成して試してみました。今度は,再圧縮してリネームしたファイルをPPTで正常に開くことができます。

以上のことから,展開や再圧縮の際にユーザが制御できない「データの損失や追加」が起こるかもしれない,ということがわかりました。将来,OSやPythonの仕様が変更されたときに,開発したコードが正常に動かなくなる可能性があります。

<閉じる>

使ったツール

使っているツールは以下のようになります。

  • PythonとVisual Studio Code
    Pythonの編集や実行にはVisual Studio Code(以下,VSCode)を使った。モジュールは標準に備わってiいたり無償でインストールできたりするものだけを使っている。
  • XMLエディタ,XMLEDITOR.NET
    XMLコードの作成や編集ができるフリーソフト。pptxで使われているxmlファイルはテキストファイルなので,Windowsのメモ帳などで開くことができる。しかし,1行目を除くと改行もインデントも無い文字列なので非常に見づらい。そこで,XMLエディタを使ってファイルを開き,整形されたテキストを印刷して使う。つまり「ビューワ」としての機能のみを利用している。(XMLEDITOR.NETを選定した理由は,“何となく”でした。国産のフリーのXMLエディタの中では使いやすいのかもしれません。)
  • ファイル比較・マージツール,WinMerge
    オープンソースのツール。2組のファイルやフォルダの間の差異を見つけるために使用。同じpptxファイルを基に音声データの無いものと有るものを作り,zip展開して,ファイルのどこが異なるか調べるために使った。

pptxファイルについてわかったこと

現時点で以下のことがわかっています。

pptxファイルの中身

zip展開したpptxファイルをファイルエクスプローラで表示すると図のようになります。

filename.pptxという名前のpptxファイルを展開したときの構成

この図では,オーディオデータを追加する際に書き換えたり追加したりするフォルダやファイルは赤枠や赤文字で表しています。(スライド2枚程度の非常にシンプルなpptxファイルを使って調べた結果なので,実際にはもっと多くの種類のフォルダやファイルがあるかもしれません。)

フォルダ(ディレクトリ)の構成は以下のようになっています。

  • 拡張子“.pptx”を除いた名前を持つフォルダがルートディレクトリとなっている
  • ルートディレクトリの直下に1つのファイルと3つのフォルダがある
    ファイルは[Content_Types].xml,フォルダは,ppt,docProps,_rels。
    オーディオデータの追加で使うのは,[Content_Types].xmlとppt内のファイル
  • ppt/slidesフォルダとppt/mediaフォルダの中のファイルを変更
    mediaフォルダの中にはjpegなどの画像ファイルやwav形式のオーディオファイルを格納する。これらのメディアファイルを使わないpptxファイルではmediaフォルダは存在しない。
    slidesフォルダの中には各スライドの情報を記述するxmlファイルslide1.xml,slide2.xml,・・・がある。さらに_relsというフォルダがあり,その中にあるslides1.xml.rels,slides2.xml.rels,・・・は,対応するslideのxmlファイル内の記述と外部のデータとの関連(relation)を記述するテキストファイルになっている。

xmlファイルにはslide1.xml,slide2.xmlのように,slide + スライド番号 + .xmlというファイル名が付けられています。xml.relsファイルも同様です。以下の説明では,これらのファイルを,“slide.xml”,“slide.xml.rels“のようにスライド番号を略して表すことにします。

どのファイルのどこを変更するか

PPTを使って,音声データを付加している/していない,クリック起動のアニメ有り/無し,など何種類かのpptxファイルを作りました。これらのpptxファイルをzip展開して,中のファイルをXMLエディタで整形して印刷し,何が違うか観察します。しばらく眺めていると,だいたいアタリがついてきます。

この段階でPythonでxmlファイルを書き直すコードを作って,見た目はいい感じにslide.xmlファイルを直してみたのですが,再圧縮したpptxファイルは動作しません。slide.xmの中lで直すべきところがまだありそうです。さらにslide.xml以外にもファイルはたくさんあるので,確認のためファイル比較ツールのWinMeargeを使って,違うところを見つけていきます・・・。

ということを繰り返して,オーディオデータを埋め込むために,どのファイルのどの部分を書き直せばよいかが,少しずつ見えてきました。現時点での結果をまとめると,変更や追加が必要なファイルは以下のようになります。

  • ppt/slides/の中のslide1.xml, slides2.xml, ・・・:
    スライド毎に,切替えやクリックの際の効果(effect)としてオーディオデータを指定する記述を追加する。オーディオデータはユーザが作成したwav形式のファイル名で指定する。また,追加するeffectにはpptxファイルに埋め込まれるオーディオファイルの実体との関連を表すIdである“rId”を与える。
  • ppt/slides/_relsの中のslide1.xml.rels, slides2.xmlrels, ・・・:
    対応するスライドのxmlファイル内の記述と外部のデータの関連(relation)を示すファイル。rIdに対応する実際のオーディオファイルを記述するコードを追加する。
  • ppt/media/を作成し,wav形式のファイルを格納
    wav形式のファイルはプレゼンテーション内部での通し番号を付けたaudio1.wav,audio2.wav,・・・というファイル名に変更してmediaフォルダの中にコピーする。
  • [Content_Types].xml:
    wav形式のオーディオファイルを使うことを示す記述を追加する。この記述はオーディオファイルの名前や個数などによっては変化しない。 

変更の具体的な方法は,「スライド切替えのオーディオデータ」と「クリック起動のアニメへの音声データ」で少し異なっています。まず,動作確認できた「スライド切替えのオーディオデータ」について解説します。

スライド切替えオーディオデータの付加 

PPTのGUIで音声データを設定する場合は,下の図のようになっています。「画面の切り替え」リボンを選択し,サウンドの選択ボックスで音声データを指示します。ここでは,testPy_01_00.wavというwav形式のファイルが音声データとして指定されています。

PPTの操作画面で「画面切り替え」リボン > サウンド > …でオーディオデータを付加

私が講義で使っているPPTでは,画面切り替えの設定はデフォルトのままで,クリックで切り替えるようにしています。口頭での説明のタイミングも,ほぼ切り替えの前後でおこなっています。ですから,画面切り替えのクリックのタイミングで音声を発生させるのは,割と自然だと考えています。

画面切り替えのタイミングにオーディオデータを付加するためのファイル操作は,クリックで動作するアニメへの音声付加のものよりはシンプルです。それで,基礎的なことを確認するための勉強を兼ねて,画面切り替えへの音声付加のコーディングを先に完成させることにしました。

slide.xmlを変更する

画面切り替えのオーディオデータを埋め込んだpptxファイルのslide.xmlの一部を下のリストに示します。XMLエディタで改行とインデントを挿入してあります。

青い文字は,オーディオデータが無い場合のコードです。黒と赤の文字がオーディオデータを埋め込んだときに挿入されていたコードを示します。赤い文字が,埋め込む音声データのファイル名や他のファイルとの関連に応じて変更すべき部分です。

  <p:clrMapOvr>
    <a:masterClrMapping />
  </p:clrMapOvr>
  <mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
    <mc:Choice xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main" Requires="p14">
      <p:transition spd="slow" p14:dur="2000">
        <p:sndAc>
          <p:stSnd>
            <p:snd r:embed="rId2" name="testPy_01_00.wav" />
          </p:stSnd>
        </p:sndAc>
      </p:transition>
    </mc:Choice>
    <mc:Fallback>
      <p:transition spd="slow">
        <p:sndAc>
          <p:stSnd>
            <p:snd r:embed="rId2" name="testPy_01_00.wav" />
          </p:stSnd>
        </p:sndAc>
      </p:transition>
    </mc:Fallback>
  </mc:AlternateContent>
</p:sld>
 

オーディオを埋め込んでいない状態のslide.xmlの中で,“</p:clrMapOvr>”という文字列を見つけ,その直後に,”<mc:AlternateContent”で始まり”</mc:AlternateContent>”で終わる文字列を挿入します。

さらに,挿入した文字列の中にある<p:snd r:embed=”rId2″ name=”testPy_01_00.wav” />の部分の変更が必要です。

“rId2”は,ユーザが埋め込む音声データに与えるrelation Id(“関連識別子”)として与えた文字列で,slide.xmlの中にある関連付け要素によってrId3,rId4,・・・のように数字部分が変化していきます。

“testPy_01_00.wav” は,埋め込むオーディオデータとしてユーザが用意したファイルの名前です。

以上の2か所を変更した文字列を“</p:clrMapOvr>”の後ろに挿入すればよいことになります。

slide.xml.relsファイルを変更する

同じpptxファイルのslide.xml.relsの一部を以下に示します。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/audio" Target="../media/audio1.wav" />
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml" />
</Relationships>

オーディオを埋め込んでいない状態のslide.xml.relsの中で,”relationships”>”という文字列を見つけ,その直後に,”<Relationship Id=”で始まり”audio1.wav” />”で終わる文字列を挿入します。

挿入する文字列は,“Id=”rId2”と,“audio1.wav”の2か所の変更が必要です。

“rId2”は,slide.xmlの中のrIdの文字列と等しくします。一方,“audio1.wav”はpptxファイルに埋め込むwav形式のオーディオファイルの名前になっています。この実体は,ユーザが用意したファイルtestPy_01_00.wavと同じものです。埋め込まれるオーディオファイルの名称は,audio + ファイル番号 + .wavという規則で生成されます。ファイル番号は,プレゼンテーションの中では1から始まり,オーディオデータを追加するたびに1ずつ増加される「通し番号」になっています。このファイル名は一度決めるとスライドの順番を入れ替えても変わらないのかどうかは未確認です。

ppt/mediaフォルダにオーディオファイルを配置

オーディオファイルは,ppt/mediaフォルダの中に配置します。下の図は,ppt/mediaフォルダの中身をファイルエクスプローラで表示したものです。

ppt/mediaフォルダの中にaudio1.wavファイルを配置する

オーディオや画像を埋め込んでいないpptxファイルではppt/mediaフォルダは存在しません。Pythonコードでは,フォルダが無い場合は作成し,そこにユーザが用意したオーディオファイルをリネームして配置するようにします。

[Content_Type].xmlファイルを書き直す

[Content_Type].xmlは,展開したpptxディレクトリの直下に置かれています。その冒頭の部分を以下に示します。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="jpeg" ContentType="image/jpeg" />
  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />
  <Default Extension="xml" ContentType="application/xml" />
  <Default Extension="wav" ContentType="audio/x-wav" />
  <Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml" />

[Content_Type].xmlファイルの内容

“<Default Extension=”wav” ContentType=”audio/x-wav” />”の部分は,オーディオファイルを埋め込んでいないpptxファイルでは存在しません。

ファイルの書き換えは簡単です。”application/xml”/>”を見つけ,その直後に
“<Default Extension=”wav” ContentType=”audio/x-wav” />”を挿入すればよいだけです。挿入する文字列を変更する必要はありません。おそらく,この[Content_Type].xmlファイルは,プレゼンテーションにどのような形式のコンテンツが含まれているかを示しているのでしょう。

<Default Extension=”wav” ContentType=”audio/x-wav” />を挿入しない場合,pptxファイルを開いたときにエラーになるのか,あるいは自動て修復してくれるかどうかなどについては未確認です。

クリックで起動されるアニメーションへのオーディオデータの付加 

PPTによるプレゼンテーションでは,文字列やシェイプオブジェクトを出現や消去させたり動かしたりする「アニメーション」が可能です。アニメーションは通常はマウスクリックで開始されます。

私の講義のスライドは,アニメーションを多用しています。1枚のスライドの中で,かなり細かく段階的に表示を変えていきます。このため,クリックの回数の方がスライド切替えよりずっと多くなります。

下の図のスライドは3つのテキストボックスがあり,それぞれがクリックで出現するようになっています。GUIで音声データを設定する場合は,下の図のようアニメーションウインドウを開き,スライド毎に設定していきます。設定はアニメの開始をクリック時にするか,それとも直前の動作と同時または後で起動するのかを選択できます。また,このように前の動作で(自動で)起動する場合の遅れのタイミングなども調整できます。

「アニメーションウインドウ」 オプション効果としてオーディオデータを付加

さらに,「効果のオプション…」を選ぶことでアニメーションに様々な効果をオプションで追加できます。その効果の1つに,サウンドの追加があり,ユーザが作成したwav形式のファイルも使うことができます。PPTへのナレーションの付加は,このアニメーションのオプションを利用しています。

効果オプションの設定ボックス

スライド切替のオーディオはスライド1枚につき高々1回しま発生されません。一方,クリックで起動されるオーディオは,1枚のスライドの中で複数回出現します。また,スライドのxmlファイルから読み取るべきパラメータも1つ多くなります。このため,スライド切替えオーディオの挿入より処理が少し複雑になります。

自動でオーディオを追加するための処理は,スライド切替えのときと同じです。異なるのは,slide.xmlの書き換えの内容と,1つのスライドに対し複数のオーディオファイルを埋め込むことの2つです。

slide.xmlを変更する

クリック起動されるアニメーションにオプション効果としてオーディオデータを付加したときのslide.xmlのクリックに関連する部分を下のリストに示します。

青い文字は,オーディオデータが無い場合のコード,黒と赤の文字がオーディオデータを埋め込んだときに挿入されていたコードを示します。赤い文字が,埋め込む音声データのファイル名や他のファイルとの関連に応じて変更すべき部分です。

   <p:childTnLst>
     <p:par>
       <p:cTn id="5" presetID="1" presetClass="entr" presetSubtype="0" fill="hold" grpId="0" nodeType="clickEffect">
         <p:stCondLst>
           <p:cond delay="0" />
         </p:stCondLst>
         <p:childTnLst>
           <p:set>
             <p:cBhvr>
               <p:cTn id="6" dur="1" fill="hold">
                 <p:stCondLst>
                   <p:cond delay="0" />
                 </p:stCondLst>
               </p:cTn>
               <p:tgtEl>
                 <p:spTgt spid="4" />
               </p:tgtEl>
               <p:attrNameLst>
                 <p:attrName>style.visibility</p:attrName>
               </p:attrNameLst>
             </p:cBhvr>
             <p:to>
               <p:strVal val="visible" />
             </p:to>
           </p:set>
         </p:childTnLst>
         <p:subTnLst>
           <p:audio>
             <p:cMediaNode>
               <p:cTn display="0" masterRel="sameClick">
                 <p:stCondLst>
                   <p:cond evt="begin" delay="0">
                     <p:tn val="5" />
                   </p:cond>
                 </p:stCondLst>
                 <p:endCondLst>
                   <p:cond evt="onStopAudio" delay="0">
                     <p:tgtEl>
                       <p:sldTgt />
                     </p:tgtEl>
                   </p:cond>
                 </p:endCondLst>
               </p:cTn>
               <p:tgtEl>
                 <p:sndTgt r:embed="rId2" name="testPy_01_01.wav" />
               </p:tgtEl>
             </p:cMediaNode>
           </p:audio>
         </p:subTnLst>
       </p:cTn>
     </p:par>
   </p:childTnLst>

注目していただきたいのは,青い文字で表示された部分の上から3行目です。

<p:cTn id=”5” presetID=”1″ presetClass=”entr” presetSubtype=”0″ fill=”hold” grpId=”0″ nodeType=”clickEffect”>

nodeType = “clickEffect”という記述があることから,クリックに関連した記述であることがわかります。この文字列の初めの方に,id=”5″となっているところがあります。この“5”を緑の文字で強調していますが,これは,このクリックを表すid(indentifier)番号のようです。そして,これと同じ値(“5”)が黒と赤の文字で表した,オーディオ付加に伴って挿入されるXMLコードの7行目に, <p:tn val=”5” />のように現れています。

説明がグダグダになってしまいました。現在,動作確認できているPythonコードでは,slide.xmlに対して以下のような処理をしてn番目のクリックにオーディオを付加しています。

ⅰ)nodeType=”clickEffect”という文字列のn番目のものを見つける

ⅱ)見つけたnodeType=”clickEffect”から遡って<p:cTn id =” を探索する
 初めに見つけた<p:cTn id =” の後にあるクリックのid番号を読み取る。

ⅲ)nodeType=”clickEffect”から後方を探索し,初めの</p:childTnLst>を見つける

ⅳ)</p:childTnLst>の直後に<p:subTnLst>~</p:subTnLst>の文字列を挿入
 黒字で書かれたコードです。挿入する前に赤文字で書かれた3つの部分を書き直しておきます。
 クリックのid,RelationsのrId,ユーザが作成したwav形式ファイルの名前,の3つです。

以上を,オーディオを付加するクリック起動のアニメーションの数だけ繰り返します。

slide.xml.relsファイルの変更とオーディオファイルの配置

slide.xmlファイルの記述と埋め込みデータなどとの関連付けをするslide.xml.relsの書き換えや,ppt/mediフォルダへのwav形式ファイルの配置などは,スライド切替えオーディオの場合と同じです。異なるのは,スライド1枚につき複数回のデータ追加とファイル追加をする可能性がある点だけです。

繰り返しになりますが,ppt/mediフォルダに書き込むwav形式ファイルは,audio1.wav,audio2.wav,・・・のように,プレゼンテーション全体の中での通し番号で識別されていることに注意が必要です。

動作確認

スライド1枚での中に3つのアニメーションで出現するテキストボックスがある,というシンプルなpptxファイルに自動でオーディオデータを付加できることを確認しています。

元々の仕様では,クリックではなく,直前の動作と同時または直後に起動される「自動起動」のアニメーションにオーディオデータを付加するようになっています。私自身が使うには,自動起動のオーディオはほとんど使わないので,このままでもよいのです。でも,使えるようにしておくと便利かもしれないので,それについては,おいおい考えることにします。

PPTファイルに音声を自動で埋め込む その3 Pythonによる実装 に続く

コメント