授業 | TamLab 授業のおもちゃ箱 https://tamlab.fc2.page 情報系科目の講義資料とツールを公開 Sun, 07 Jul 2024 09:10:48 +0000 ja hourly 1 https://wordpress.org/?v=6.7.2 PowerpointとiSpring Suiteでweb教材を作る https://tamlab.fc2.page/category-materials/2285/ https://tamlab.fc2.page/category-materials/2285/#respond Sun, 07 Jul 2024 06:56:05 +0000 https://tamlab.fc2.page/?p=2285 PowerpointとアドインソフトiSring Suiteを使ってwebで閲覧できる教材を作る方法を紹介します。

Powerpointスライドショーを講義に使う

私はPowerpointとプロジェクタを講義に使っていて、黒板やホワイトボードによる板書はほとんど使いませんでした。この記事を読んでいる方が教員でしたら、Powerpointのメリットやそれを活かした講義のノウハウに関しては、私などよりははるかによくご存じだと思います。

<注を読む>

Powerpointとプロジェクタは電子黒板と同じように使うことができます。私は,黒板やホワイトボードに書いたり消したりする時間を省くことを目的に採用しました。浮いた時間は演習など授業内容の理解や定着のために使うことができます。また、アニメーションなどの効果を使うことでスライドショーに動きを付けることで学生の興味を引き付け、授業を効率よく進行できるようになります。講義進行の計画もたてやすくなり,内容のアップデートも楽になりました。

また,作った資料をwebで閲覧できるようにすると,学生さんの自学習用に利用でき,講義内容の記録保管所(アーカイブ)を築くのに使うこともできます。

<注を閉じる>

スライドショーの例を示しましょう。音声ナレーションだけをつけたものと,音声と字幕のナレーションを併用するものの2つです。下の画像をクリックすると新しいタブでスライドショーが始まります。

      音声ナレーションのみ                 音声+字幕

スライドショーの開始時に、左のようなダイアログボックスが表示される場合があります。1枚目からスライドショーを実行する場合は“NO”を、前回の続きから再開する場合は“YES”をクリックします。

音声+字幕のものは、自作のナレーション付加ソフト(Powerpoint Narrator)の不具合のため、スライド画面の縦幅が大きくなっています。また、再生するデバイスによってはナレーションの一部が無音になってしまうかもしれません。

私が過去の講義で使ったPowerpoint教材のスライドショーは、以下のサイトで閲覧できます。在籍中の教材をそのまま並べただけのシンプルなサイトです。ナレーションもついていません。

<http://tamlab.web.fc2.com/>

HTML5変換

これらのファイルはPPTファイルそのものではなく,そのスライドショーをwebで閲覧できるようにHTML5形式のファイルに変換したものです。こうしておくと、次のようなメリットがあります。

  • 機種に依存しない
    ブラウザが搭載されているなら、Windowsマシン、Macintosh、スマートフォン、・・・など機種を問わず閲覧できます。
  • ネットワークにつながっている機器があればどこでも講義ができる
    web接続できる機器があれば、PCやUSBメモリなどを持ち歩かなくても、講義やプレゼンができます。実際、講義では、ネットワーク経由で複数のスライドショーを同時進行させたことがあります。
  • webで広く閲覧してもらえる
    これが一番大きなメリットかもしれません。講義だとせいぜい数十人ですが、webを使えば全世界が相手になります。

PowerpointスライドショーのHTML5への変換には、幾つかの方法があり,私が試してみたのは以下の2つです。

  • Powerpointのアドインソフトを使う
    インストールすると、Powerpointに新しいメニューが組み込まれ,そこから変換の作業を始めることができます。
  • webサービスを使う
    webサイトにPPTファイルをアップロードすると変換してくれるサービスがあります。

私が使っているのは、有料のアドインソフトであるiSpring Suiteです。iSpring SuiteはPPTスライドショーのHTML5変換だけでなく、e-Learning教材を構築するための様々な機能を持っています。HTML変換だけならiSpring Converterを使うこともできます。web対応教材を作ろうと考えた時点では、使えるソフトやサービスの中で最も変換の精度が高いものだったため,これを採用しました。

iSpring社のサイト (表示言語は英語です。ブラウザの翻訳機能を使うと日本語表示になります。)

HTML5変換のwebサービスにも無料・有料の両方があるようです。ソフトをインストールする必要がないのは魅力ですが、無料の場合、スライド枚数の制限があったりスライドショーの画面にウォーターマークが表示されたりしたかもしれません。

PPTXファイルであれば本質的にはXMLファイルなので、これをHTML5に変換するソフトを自作できるだろうと思います。でも使える技術情報を見つけるところまでいっていません。Aspose社の製品を使うと自作できそうですが,ソフトの年間契約料がすごく高いみたいです。

手順

PPTファイルからweb教材を作るおおまかな手順を紹介します。

1)Powerpoint教材を作る
 Powerpointでスライドショーの教材を作ります。テキストボックスや図形でスライドを作り、アニメの効果を付けたり音声を付加します。アニメの効果として音声ナレーションや効果音を付けることは手動でも可能です。私が作成中のソフト「Powrpoint Narrator(自称)」を使うと、音声ナレーションや字幕を自動で付けることができます。(手間ひまかければ、手動の作業だけで同じことができますが・・・。)

2)HTML5ファイルに変換する(publish)
 iSpring Suite(またはiSpring Converter)を使ってHTML5ファイルを作成します。(試してはいませんが、Youtube用の動画にすることもできるようです。)

3)webサイトにアップロードする
 FFFTPなどの転送ソフトを使って,webサイトにファイルをアップロードします。私のwebサイトでは,WordPress用のプラグイン(Insert or Embed Articulate Content into WordPress)を使って,メディア格納フォルダにアップロードしたファイルをサイトに埋め込んでいます。

iSpring Suiteの使い方

Powerpointアプリの中からiSpring Suiteを起動してHTML5ファイルを作る手順を、スライドショーで説明します。以下の画像をクリックしてご覧ください。

iSpring Suiteの使い方

この操作によって、プロジェクトの名前を付けたフォルダの中に、index.htmlというファイルとdataという名前のフォルダが格納されたものが作られます。このデータをFFFTPなどのファイル転送ソフトで所望のwebサイトにアップロードします。また、変換後のオプションとしてzip圧縮したファイルを作っておくと、WordPressのプラグインを使ってアップロードできます。zipファイルを使う方法は簡単ですが、ファイルを格納する場所は自由には選べません。

詳しい手順や設定はiSpring社関連のサイトなどからリンクできるオンラインヘルプを参照してください。

WordPressのプラグインを使ったアップロード

プラグインInsert or Embed Articulate Content into WordPressを使うことで、ページの中にスライドショーのウインドウを埋め込むことができます。

プラグインの使い方について、備忘録として書いておきます。(久しぶりに使ってみたら、手順を忘れていてオタオタしたので。)

e-Learningブロックを挿入
 ブロック挿入の際、選択肢の中から“e-Learning”を選ぶ。

e-Learningブロックの操作開始
 挿入されたブロックの“アップロードボタンをクリックする。

zipファイルを指定
 “アップロード”をクリックすると左のような表示になる。CHOOSE YOUR ZIP FILEをクリックし、iSpring Suiteで作成したzipファイル
を選ぶ。

zipファイルをアップロード
 目的とするzipファイルが選択されたことを確認したら、“UPLOAD”ボタンを押す。

Playerを挿入
 “Upload Complete!”を確認したら、ウインドウを下にスクロールする。

 “挿入”ボタンのところまでスクロールしたら、これをクリックする。Playerの選択肢が表示されているが、無料版のプラグインの場合はiFrameしか選べない。

作業が終了すると、e-Learningブロックは左のような表示になる。やり直す場合は“削除”ボタンをクリックする。

e-Learningブロックを使ってスライドショーを埋め込んだ場合は、以下のようになります。

以上、PowerpointのアドインiSpring Suiteを使って、WordPressのページにスライドショーの機能を持たせる方法を説明しました。

webページで動きのある説明をするために、ビデオを埋め込む方法がよく使われているようです。しかし、スライドショーの方が使い勝手が良いと感じています。閲覧者自身が進行をコントロールできるからです。

webサイトの説明と授業で使うスライドショーとでは違う工夫が必要だと思いますが、実際に使いながら目的に応じたスライドショーの作り方を考えていくつもりです。

<お問い合わせはこちらから>

]]>
https://tamlab.fc2.page/category-materials/2285/feed/ 0
PPTXファイルに字幕を付ける 使い方の説明 https://tamlab.fc2.page/category-materials/2218/ https://tamlab.fc2.page/category-materials/2218/#respond Tue, 09 Apr 2024 10:18:59 +0000 https://tamlab.fc2.page/?p=2218 PPTXファイルに音声や文字のナレーションを埋め込むWindowsPC用ソフトを作っています。オーディオのナレーションを付けることを目的として作り始めたソフトでした。しかし字幕を付けられるようにしてみると、これだけでも使い道が多そうな気がしてきました。

そこで、今回の投稿ではスライドショーに字幕を付けるやり方の例を紹介します。せっかくなので開発中のソフトで使った字幕付きスライドショーを使うことにします。

なお、このソフトを「このアプリ」とか「開発中のソフト」などと呼んできました。Pythonファイルの名前は“AddAudio2pptx.py”などとしていましたが、何かもう少しそれっぽい名前で呼ぶことにしましょう。適切かどうかわかりませんが、“Powerpoint Narratorパワーポイントナレーター)”と呼ぶことにします。注)

<注を読む>

ChatGPTで、以下のように質問しました。

「Powerpoint Narratorという名前のソフトウェアがありますか? もしあるとすれば、どのようなことをするソフトウェアだと考えられるでしょうか?」

回答は以下のようでした。

「2022年1月時点では、Powerpoint Narratorという名前のソフトウェアは存在しませんでした。ただし、Microsoft PowerPointには、スライドのナレーションを録音する機能があります。これにより、プレゼンテーションを行う際にスライドごとに音声を付けることができます。この機能を利用することで、スライドショーを再生するときに自動的にナレーションが再生されるようになります。また、Microsoft PowerPointには、アクセシビリティを向上させるための機能もありますので、それらの機能の組み合わせによって、スライドのナレーションを作成するソフトウェアとしての機能を果たすことができるかもしれません。」

<注を閉じる>

では、以下のように3つに分けて説明をしていきます。

PPTXスライドを準備
スクリプトを作る
ナレーションを付ける

PPTXスライドを準備

Powerpoint Narratorは、現時点ではPPTXのスライドの自動生成や生成をサポートする機能は持っていません。まずはじめにナレーションを付ける元のPPTXファイルを準備する必要があります。

ここで想定しているのは、1枚のスライドの中に複数ステップのアニメ効果を持つようなPPTスライドショーです。数行の文字列が一度に表示されるだけのスライドが延々と続くようなスライドショーでは字幕を付けるだけのためならPowerpoint Narratorは不要です。PPTアドインのiSpring Suiteを使えばPPTのノートの記述を字幕として表示するHTMLスライドショーが作れてしまうからです。

スライドの準備についての説明は次のスライドショーでご覧ください。

1)PPTXスライドを準備

スクリプトを作る

PPTXファイルの準備ができたら、スライドショーの「スクリプト」を作ります。スクリプトは、スライド切替えやマウスクリックを示す文字列と、これらに同期して発声や表示をするナレーションのテキストを順番に並べたものです。スクリプトはテキストファイルの形で作成し保存されます。

スクリプトは複数の作り方が可能です。今回紹介するのは、現時点で一番楽で失敗が少ないと思われる方法です。

次のスライドショーをご覧ください。

2)スクリプトを作る

スクリプトは、以下の規則に従って作ります。

  • 行単位で動作を記述
    開始からリターンコードまでで1単位とする。
  • エスケープ行
    以下の文字列(全て半角)が先頭にある行は,スライド切り替えやアニメーションのためのマウスクリック(あるいは矢印キー操作)を表し,音声合成は実行しない。
    • #Snn:スライド番号を示す。nn:01~99(2桁の数字)
      例:#S01,#S20など。
    • #mm:1つのスライド内でのアニメーションのクリック番号。mm:00~99
      #00はスライド切り替え時のナレーションを表す。
      例:#00:スライド切り替え時のナレーション,#01:1番目のクリックのナレーション。
    • #nn_mm:nn番目クリックに続く自動アニメーションの番号を示す。mm:01~99
    • %:コメント行
  • 空行は無視する
  • 記述のルール
    スライド番号とクリック番号を順番に並べる。スライド番号とクリック番号はエスケープ文字列を 行頭に持つ行で指定し,改行後の「コメント行でも空でない行」がナレーションとして処理される。

上記のルールは,AutoHotKeyを使ったアプリ開発(PTファイルに音声を自動で埋め込む その1 AHKによる実装)の際に作ったものです。現在のアプリはPythonでコーディングされていて,上のルールから外れた記述でも動作することがわかっています。以下にまとめます。

  • スライド切替え時のナレーション
    #00 という文字は略してもよい。例えば,2枚目のスライド切替え時のナレーションは,“#S02”の次の行に記述すればよい。
  • ナレーションを付加しないスライドやアニメーションの番号は略してもよい
    例えば,3枚目のスライドの2番目のクリックのみにナレーションを付加する場合は,
    #S03
    #02
    ×××・・・・・(ナレーションのテキスト)
    というスクリプトで動作する。
  • コメント行
    #Snnや#mmなどのエスケープ文字列を先頭に持つ行は,これらの文字列の後ろに1つ以上の空白文字があれば,その後ろに挿入した文字列は改行コードまでが無視される。これをコメント文字列として使うことができる。

ナレーションを付ける

スクリプトに従ってPPTXスライドショーにナレーションを付けた新しいPPTXファイルを作ります。

手順は次のスライドショーをご覧ください。

3)ナレーションを付ける

Powerpoint Narratorの操作ウインドウにある“Audio”と“Caption”というラベルのついた2つのチェックボックスで、付けるナレーションの種類を選択します。Audio(音声ナレーション)とCaption(字幕)の両方を同時に付けることも可能です。ただし、音声ナレーションを埋め込むためには、オーディオファイルをあらかじめ作成しナレーションフォルダ(上の例では“PPT_Test”という名前)に格納しておく必要があります。また、既に字幕やオーディオのナレーションを付けてあるPPTXファイルを対象とした場合、どうなるかはこれから確認する予定です。

感想

  • ちょっと疲れるかも
    今回の投稿では、Powerpoint Narratorで字幕をつけたPPTXファイルを説明に使いました。スライドの枚数が少し多かったので閲覧する人は疲れるかもしれません。
  • 字幕の問題
    スライドの画像と字幕が、両方とも視覚を使うことからくる「わかりにくさ」の問題は、少なくとも私は感じました。オーディオのナレーションの方が速く頭に入りそうです。また、字幕の背景の色やスライド本体との区切りの方法など、改善すべき点があります。
  • スライドの枚数は適切か?
    講義の場合には、手元に配布資料もあり、それに目を通しながらスライドショーを閲覧できるので、スライド枚数が多くても分かりにくくなることはなさそうです。しかし、webページの場合は、スライドを見ているときにページの他の場所を見るのは難しくなります。スライドが切り替わってしまうので、何枚か前の画像を記憶していることも難しくなるでしょう。スライドの枚数や画像の提示の仕方については、もう少し研究が必要になりそうです。
  • 作る方も疲れる
    スライドショーが長いと、見るほうも疲れるかもしれませんが、作る方も疲れます。一番大変なのは、PPTスライドショーを作る過程です。この過程に費やす時間を短くして、しかも内容が伝わり易いスライドを作ることをサポートするソフトが必要だと感じました。例えば、よく使うアニメのシーケンスを自動生成する機能とか、です。
  • こんな機能が欲しくなる
    次に時間がかかるのが、スクリプトの作成です。これは、スライドショーをクリックにより進行させながらナレーションを喋って録音するような機能を、Powerpoint Narratorに持たせることで改善できそうです。録音した音声をテキストに変換すれば、スクリプトの編集の時間を短くできるかもしれません。(勉強しないといけないので、だいぶ先になりそうです。)
    また、録音したナレーションだけで良い場合もあると思います。それに対応するようにコードを変更するのは、そんなに難しくはなさそうです。
]]>
https://tamlab.fc2.page/category-materials/2218/feed/ 0
PPTXファイルを直接書き換えて音声や字幕を付加 https://tamlab.fc2.page/category-materials/2159/ https://tamlab.fc2.page/category-materials/2159/#respond Thu, 04 Apr 2024 08:33:18 +0000 https://tamlab.fc2.page/?p=2159 PPTXファイルに音声や文字のナレーションを埋め込むWindowsPC用ソフトを作っています。コーディングに時間が取れなかったこともあって、前回の投稿からだいぶ間が空いてしまいました。でも開発は「遅々として進む」という状況で、幾つかの機能を付け加えてきました。この数週間での大きな成果(と自分で思っていること)は「クリックと連動する字幕を表示できるようになった」ことです。

作ったスライドショーの例

このソフトで字幕を付けたPowerpointスライドショーの例を以下に示します。自分で動かしてみてください。表示画面下の矢印ボタンのクリック、スライド画面内にポインタを置いた状態でのマウスクリック,あるいは矢印キーなどでスライドショーが進行します。

字幕をつけたスライドショーの例

実際の講義でも,この例と同様に数式の変形過程などを細かなステップに分け,何回もクリックしてアニメで表示し,口頭で説明を加えています。このため,このPPTスライドショーを音声や字幕が付いていないままweb用に変換しても,どういう意図で細かくステップ分けしているかを分かっていただけない可能性があります

<注を読む>

なお,上のPPTスライドショーは,iSpring社のPPTアドインであるiSpring Suite 10を使ってHTML5ファイルに変換しています。このソフトは有料ですが,同様のことをするフリーのアドインや,PPTファイルを変換してくれるフリーのwebサイトもあります。

<注を閉じる>

このソフトウェアでできること

  • 音声ナレーションと字幕をスライドショーに付加
    PPTXのスライドショーに音声や字幕によるナレーションを自動で(つまりPPTのGUI操作をしないで)付加できる。また「かんたん!Aiトーク5」を制御して、テキストから自動的にオーディオデータを作ることができる。
  • スクリプトにより自動実行
    付加される音声ナレーションや字幕は、スライドの進行やアニメを制御するマウスクリックなどと同期している。この動作は、スライド切替え、マウスクリック、ナレーションの内容をテキストで表して順番に並べた「スクリプト(台本)」に従って作られる。 
  • サポート機能
    スクリプトの作成や、オーディオデータの確認と再編集をサポートする機能などが実装されている。

字幕を付けることの意義

もともとは,私自身の講義の記録(アーカイブ)と,学生さんの自習に使うことを考えてソフトを作り始めました。そのときは,字幕は「おまけ」の機能と考えていたのです。講義でスライドショーを見せる場合は,教員による口頭での説明があるので字幕は不要です。自習の場合も字幕は邪魔かなと考えていました。スライドは視覚により情報を伝えています。字幕という視覚による情報伝達がさらに加わると,「見て読んで理解する」という視覚による認知の負担が増えてしまいます。それで,あまり教育の現場での需要は少ないかも,と考えました。

<注を読む>

字幕のような視覚情報が増えても学習の効率は低下しない,と主張する人もいます(星 友啓:脳を活かすスマホ術 スタンフォード哲学博士が教える知的活用法、朝日新聞出版(2023))。でも、多様な学生を対象とした調査結果を見てからでないと、判断は難しいと思います。

<注を閉じる>

しかし,字幕だけで済むならファイルの容量を減らせます。また,聴覚が弱かったり、口頭での説明の理解に時間がかかったりする学生さんには助けになるでしょう。web上で閲覧できるスライドショーなら学習する人が自分のペースに合わせた進行や繰り返しができます。ですから視覚的負担の増加はそれほど問題にならないと考えられます。

スライドショーに字幕を付けることの効用や弊害,どのような使い方が効果的か,などは今後の研究課題だと思います(既に研究されているかもしれませんが)。簡単に字幕を付けられるソフトがあれば,そのような研究の助けになると思います。

技術的な内容の説明に移るの前に,背景となっている事情について説明します。過去の投稿とダブる部分もあります。興味のある方は目をとおしてください。

<詳しく読む>

技術的には・・・PPTXファイルを直接書き換えている

GUIでもできることをしている

まず,はじめに申し上げておきます。開発しているソフトは,これまで技術的に不可能だったことを可能にするようなものではありません。

アニメを起動するマウスクリックにオーディオの効果を付けたり,テキストボックスで作った字幕を出現させたり消したりすることは,全てPowerpointの標準的なGUIを使ってできてしまいます。しかし実際問題として,少しナレーションの数が増えるとGUIを使った作業は非常に時間がかかるものになります。字幕の作成も,正確な位置にテキストボックスを作成し,文字を入れ,出現と消滅のアニメの効果を付ける,ということを繰り返すので,字幕の数が少なくても大変です。

自動化ソフトでも問題あり

それでは,ソフトウェアを自動で操作するRPAツールやPythonなどで作ったGUI信号生成ソフトを使う方法はどうでしょうか? 開発しているアプリの音声ナレーションを合成したり追加する部分は、AHK(AutoHotKey)スクリプトやPythonコードによるGUI制御により作っていました。しかし動作が遅いのです。オーディオデータが揃った状態で「用意ドン!」すれば,物凄く手の速い人には負けるんじゃないかな,という感じでした。

また,動かすたびにタイミングが微妙にずれるため動作が不安定で,そのための誤動作もありました。Pythonコードにしたことで改善されましたが、それでも時々誤動作します(“時々”というのが厄介です)。一番問題なのは,動かしている間はPCを使った他の作業ができないということです(これはちゃんとしたRPAツールなら解決されている問題かもしれません)。

PPTファイルの直接操作

そこで,PPTのファイルを直接書き直す方法を採用することにしました。Pythonのライブラリにpython-pptxというものがあり,これを使うとPPTXファイルの操作ができそうです。それで大いに期待したのですが,アニメの操作が可能かどうかがよくわかりません(たぶん私の調べ方が下手なせいだと思います)。

PPTXファイルの中身は複数のxml(Extensible Markup Language)ファイルやデータファイルをまとめてzip圧縮した形になっています。したがって,解凍すればpython-pptxを使わなくてもxmlファイルの書き直しやデータの追加が可能になります。

開発中のソフトは,基本的にはzip展開されたPPTXファイルの中身を直接書き換えたりデータファイルを追加することで音声や字幕の追加を実行しています。ほとんどがテキスト操作になります。

もちろん,ファイルの書き換えなので,圧縮してPPTXファイルにしてみたらPPTアプリで開けなくなっているという危険性があります。書き換えた結果がxmlの規則に反している可能性もあります。実際,アプリの動作確認をしている段階では,出来上がったPPTXを開こうとしても「壊れているので修復しますか?」といったメッセージが出され,修復して開いても,意図した動作はしないというケースが多くありました。(ごく最近も思わぬ原因で誤動作することが分かりました。)

そこで,このアプリでは,元のPPTXファイルはそのままにして手を付けないようにしています。zip展開したフォルダ内のファイルだけを操作し,最後に圧縮して別の名前のPPTXファイルを作るようにしています。

<閉じる>

字幕付加の処理の全体の流れ

字幕を付加するソフトは,PPTXファイルをzip展開した中にあるslide1.xml,slide2.xml・・・などのxmlファイルを直接書き換えています。処理の全体を理解するため,まず,次のスライドショーをごらんになってください。

アプリは,大きく分けると以下のことをしています。

  • テキストボックス用のshapeの生成
    字幕はテキストボックスを使って表示します。テキストボックスはPPTスライド上の図形であるshapeの1つです。スライド毎にslide1.xml,slide2.xml,・・・という名前で作られたxmlファイルの中のShape Treeと呼ばれる構造の中に以下のようなxmlコードのブロックを挿入していきます。
  • アニメの記述を<p:timing>要素に追加
    スライド画面上にshapeを出現(英語版のPPTでは“enter”のようです)させたり消したり(“exit”)させるアニメのタイミングや表現方法について<p:timing>~</p:timing>の間で記述します。
  • 必要ならばスライドの寸法を変更する
    字幕用のテキストボックスはスライド画面の一番下に配置しています。元々ある図形や文字が字幕に隠されないように,スライド画面の寸法を下の方向に増やしてそこに字幕を配置するモードも用意しました。この操作はslide.xmlではなく別のxmlファイル(presentation.xml)を書き換えることになります。

字幕用shapeの生成と挿入

図形(shape)の形や位置の情報は,<p:spTree>要素の中にある<p:sp>~</p:sp>の間で記述される<p:sp>要素で表されます。実際に挿入される字幕用テキストボックスの<p:sp>要素の例は,以下のようになります。

      <p:sp>
        <p:nvSpPr>
          <p:cNvPr id="9" name="Caption 1">
            <a:extLst>
              <a:ext uri="{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}">
                <a16:creationId xmlns:a16="http://schemas.microsoft.com/office/drawing/2014/main" id="{DF488853-4B8B-58A9-2718-B1BC0D4C3A87}" />
              </a:ext>
            </a:extLst>
          </p:cNvPr>
          <p:cNvSpPr txBox="1" />
          <p:nvPr />
        </p:nvSpPr>
        <p:spPr>
          <a:xfrm>
            <a:off x="0" y="6172200" />
            <a:ext cx="9144000" cy="685800" />
          </a:xfrm>
          <a:prstGeom prst="rect">
            <a:avLst />
          </a:prstGeom>
          <a:solidFill>
            <a:srgbClr val="FFFFCC" />
          </a:solidFill>
          <a:ln>
            <a:solidFill>
              <a:schemeClr val="tx1">
                <a:lumMod val="50000" />
                <a:lumOff val="50000" />
              </a:schemeClr>
            </a:solidFill>
          </a:ln>
        </p:spPr>
        <p:txBody>
          <a:bodyPr wrap="square" rtlCol="0">
            <a:spAutoFit />
          </a:bodyPr>
          <a:lstStyle />
          <a:p>
            <a:r>
              <a:rPr lang="ja-JP" altLang="en-US" sz="1600" b="1" dirty="0" />
              <a:t>クリック1です。</a:t>
            </a:r>
          </a:p>
        </p:txBody>
      </p:sp>

Shape Treeに挿入されたコードの例

字幕用shapeの生成

朱書きされた部分をアプリが書き換えてshapeを生成します。以下,上から順に説明していきます。

  • i<p:cNvPr id=”9
    <p:cNvPr・・・からnon-visual properties要素の記述になります。“id =”の次の数字がshapeの識別番号になります。異なるshapeには異なる数字が割り当てられます。この数字はPPTの編集でshapeを作るたびに1ずつ増加して割り当てているようです。
  • name=”Caption 1
    shapeの名前です。日本語版のPPTではテキストボックスには“テキストボックス1”のような全角文字列で名前が割り当てられています。開発中のアプリでは“Caption”+“数字”という半角文字列で名前を割り当てています。数字のない“Caption”だけにしても問題なく動いていますが,後で手動で調整する際,字幕ごとに名前が異なっていた方が良いかもしれないので,異なる番号を割り当てるようにしています。
  • <a:off x=”0” y=”6172200” />
    字幕の位置です。なお,位置や寸法の単位はEMU(English Metric Unit)と呼ばれるものになっています。1インチ=914400EMU,1cm=360000EMU,文字の大きさの単位の1pt=12700EMUと,全て整数のEMU値になります。
  • <a:ext cx=”9144000” cy=”685800” />
    shapeのx方向,y方向の拡がり(extension)つまり寸法です。この例では,横幅がPPTの4×3スクリーンでの10インチ幅(9144000EMU)と同じになっています。
  • <a:srgbClr val=”FFFFCC” />
    テキストボックスの色です。24bitのRGBで指定します。この例では薄い黄色になります。その他,テキストボックスの枠線の色や幅も変更可能ですが,アプリのコードでは固定しています。
  • <a:t>クリック1です。</a:t>
    朱書きの文字が字幕として表示する文字列になります。その前の行の,
    <a:rPr lang=”ja-JP” altLang=”en-US” sz=”1600″ b=”1″ dirty=”0″ />
    で全角/半角の切り替えをしています。

Shape Treeへの挿入

必要な書き換えを行った<p:sp>要素をShape Treeに追加していきます。方法は簡単です。Shape Treeの末尾である</p:spTree>を見つけ,その直前に<p:sp>要素を挿入します。shapeの識別番号,Shape IDは,Shape Tree内を走査して見つかったshape ID値の最大値を求め,それに1を加えた値を新しいshapeに割り当てています。

<p:timing>要素の書き換えと挿入

作った字幕は,そのままではスライド下部の字幕領域に重なった状態で表示されています。これにクリックに同期して出現したり消えたりするアニメ効果を与える必要があります。このための<p:timing>要素の書き換えは,shapeの追加よりはちょっと面倒臭い操作になります。

<p:timing>要素の書き換え

以下は,字幕を表示させるために<p:timing>要素に追加されたコードブロックの例です。

                              <p:par>
                                <p:cTn id="5" presetID="1" presetClass="entr" presetSubtype="0" fill="hold" grpId="0" nodeType="withEffect">
                                  <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="8" />
                                        </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:cTn>
                              </p:par>
                            </p:childTnLst>
                          </p:cTn>
                        </p:par>

<p:timing>要素に挿入されたコードの例

朱文字はアプリが書き換える文字列,緑文字は注目してほしい箇所です。<p:par>~</p:par>で1つの単位となります。

  • <p:cTn id=”5
    挿入されるTime nodeの内容をここから記述していきます。項目ごとに識別番号idが割り当てられます。この番号は,<p:timing>要素の一番上から1で始まり,順番に1つずつ増えていきます。
  • presetClass=”entr
    図形を出現(entry,だと思います)させる効果を指定しています。PPTアプリ(日本語版)では“開始”というアニメーションのクラスに属していることを示します。“開始”クラスのアニメの効果にはフェードインなど様々なパターンがありますが,図形全体が一度に出現するシンプルな“表示”にしています。字幕を消すときは,この文字列”entr“を”exit“にします。
  • nodeType=”withEffect
    直前のクリックと同時に字幕が表示されるようにしています。
  • <p:cTn id=”6
    挿入するコードブロックの中には2つの<p:cTn>要素があるため,idを指定する箇所も2つあります。
  • <p:spTgt spid=”8” />
    コードブロックで記述している効果を与えるshapeを,その識別番号(Shape ID,spid)で指定しています。この例ではspidが”8″であるshapeを対象としています。

コードブロックの<p:timing>要素への挿入

パラメータを書き換えたコードブロックを<p:timing>要素に挿入します。手順は以下のようにしています。

ⅰ)<p:timing>~</p:timing>内でアニメを起動するクリックを見つける
 文字列“clickEffect”を見つけ,スクリプトに載っている番号のものを選ぶ。
ⅱ)“clickEffect”の後で最初に見つけた</p:par>の直後に字幕表示開始のブロックを挿入
 </p:par>がこのクリックで起動されるアニメ効果の記述の終わりになるので,その直後に字幕開始のコードブロックを挿入する。
ⅲ)次のクリックを見つけ,その後ろに字幕終了のブロックを挿入
 これで,次のクリックにより字幕の表示が終わります。このクリックが新しいアニメを起動するものになっていれば,同時に次の字幕が現れます。
ⅳ)最後に<p:timing>要素内のcTn idを割り当て直す
 Shape Treeとは異なり,<p:timing>要素内のcTn idは昇順にする必要があるので,スライド内の全ての字幕のコードの挿入が終わったところで,cTn idの再割り当てをする。

なお,ⅲ)の字幕終了ブロックの挿入については,次のクリックが見つからない場合はスライドの最後に達したことになります。ここでPPTアプリの操作者がマウスをクリックすると,次のスライドに移ります。同時に字幕も消えますので,スライドの一番最後の字幕に関しては消すためのコードブロックは入れないようにしています。クリックで最後の字幕を消し,次のクリックでスライドが切り替わるようにしても良いのですが,ただでさえ多いクリック回数を増やさないようにしました。

とりあえずここまでにしておきます。ソースコードについても忘れないうちに投稿したいです(そもそもこのwebサイト自体,私の備忘録として作ったものです)。

でも,コードがどんどんスパゲッティ化していて,とてもじゃないけど人様の目に耐えられないものになってきています。メンテナンス性も悪く,久しぶりにコーディングに取り掛かると,頭が復帰するのにすごく時間がかかってしまいます。小さなコードから紹介をしていくことになると思います。

]]>
https://tamlab.fc2.page/category-materials/2159/feed/ 0
牛車で行こう! 平安貴族と乗り物文化 https://tamlab.fc2.page/category-materials/1964/ https://tamlab.fc2.page/category-materials/1964/#respond Fri, 14 Jul 2023 06:08:34 +0000 https://tamlab.fc2.page/?p=1964 京楽真帆子:牛車で行こう! 平安貴族と乗り物文化,吉川弘文館 (2017)

平安貴族が日常使っていた乗り物「牛車ぎっしゃ」についての研究を一般の人にもわかりやすいようにまとめた本です。出版されてすぐの頃に図書館の新刊書のコーナーで手に取って「面白そう!」と借りて来たのが出会いです。

本書の「はじめに」に紹介されているように,牛車は平安時代に流行し中世後期には衰退してしまった乗り物です。ですから,牛車に関する知識なんて現代に生きる人にとっては,知らなくても実用上何の支障もないものです。

それでは筆者がなんで牛車についての研究をしたのか,と言うと,

失われた日常生活。その一つである牛車について知りたい。こうした思いから,私は牛車の研究を始めた。(p.154)

ということです。「知りたい」というのが研究を始めた動機です。なお,牛車に関しては「故実研究の他に交通史や都市史,王権論にまで発展した膨大な研究がなされている」ということです。「知りたい」と感じた人は筆者の他にもたくさんいるようです。

それでは,このような研究は,実用的には何の役に立つのでしょうか? 筆者は「古典文学を読み解くためには,牛車に関する知識は必須である。(p.1)」と書いています。でも,高校の授業なんかで牛車について教わった記憶はありません。

私は高校のときの試験の点数では,古文や漢文の方が数学や物理より上でした。自慢しているわけではありません。理数系のクラスにいたのに数学や物理の点数が古文・漢文より低かった,ということですから。試験で点数が取れたのも,文法に関する体系的な知識を身に付けていたわけではなく,知っている単語を手がかりにつじつまを合わせたストーリーをでっちあげる方法が,たまたま当たっていただけ,なのです。

入試には役立った古文や漢文ですが,高校卒業以来その知識を使う機会は全くありませんでした。でも,だからと言って「理系の人には古文や漢文の知識は不要」などとは決して思いませんでした。

乗り心地は良くなかった

牛車の話にもどします。その乗り心地はどうだったのでしょうか。イギリスのチャールズ国王の戴冠式で使っていた馬車(1762年作製)が物凄く乗り心地が悪いということでした。牛車ならスピードもそれほど出ないし乗り心地は悪くないかなという印象を持っていました。しかし,p.17に紹介されている「今昔物語集」巻第二十八第二話のエピソードを読むと,これはダメだと思いました。

加茂の祭りをどうしても見たいと考えた源頼光の家来三人が,女性専用の女車おんなぐるまに見せかけた牛車に乗り込んで出かけます。ところが,三人とも車酔いしてさんざんな目に会う,というお話です。

牛車にも車酔いがあるんだ・・・!。私は小さいころから車酔いがひどくて乗り物全般を苦手としています。牛車が使われていた時代に生きていたら,えらい難儀をしていたでしょう。(そもそも貴族として生まれないと牛車には縁がないはずですが。)

読者を「なったつもり」にさせる

本書を読んでいて,学術的な成果を一般の読者に読ませるための優れた工夫がそこかしこにあると感じました。

タイトル(「牛車で行こう!」)もそうですし「はじめに」に「ドライブ前の点検」という洒落たサブタイトルを付けたりすることからわかるように,様々な箇所で,読者が平安貴族の視点(もちろん牛車に関して)を持てるような工夫がされています。

「まずはどの車に乗るのかを決めよう」で始まる第一章も,カンヌ国際映画祭で賞を得た映画「地獄門」のシーンを出すことで興味を引き付けるようになっています。先に挙げた今昔物語の中の説話も,女車に偽装する例として紹介されています。

本書は,どんな車があるのか(第一章 車を選ぼう)を紹介し,実際に乗って移動して降りるまでのシミュレーションをし(第二章 牛車で行こう!),乗らないときもあるのか(第三章 歩くか乗るか?)について考察します。さらに,身分の高い人しか乗れない超高級車の枇榔毛車びろうげのくるまについて触れ(第四章 ミヤコを走る),同車する人達の人間関係について論じて(第五章 一緒に乗って出かけよう!)・・・と進行していきます。

ストーリー仕立てにしたり質疑応答の形で説明したりして,扱っている対象や状況に対して読者が当事者であるかのように感じさせるやり方は一般向けの技術書でも有効で,よく見かける方法です。

松平定信と輿車図考

江戸幕府の老中を務め幕政改革を主導した松平定信(1759~1829)が輿車図考よしゃずこうという輿こし(人が担いで移動する乗り物)や牛車についての研究書の執筆と編纂をしていたことを本書で初めて知りました。政治家として知られている人物ですが,老中を退いた後に,多くの文化的な活動をしていて,その1つに,この輿車図考(1804)があります。

牛車研究の金字塔」とまで呼ばれていて,膨大な文献から輿や牛車に関係する文章を抜き出して編纂し,さらには復元図も付けられているものなのだそうです。復元図があるというのは資料としての価値は非常に高いと言えます。牛車が出てくるシーンのあるTVドラマや映画を作る際には,なくてはならない資料になるでしょうね。実際,松平定信が輿や牛車に興味を持ったのは,平家物語の絵を作ることを企画していたためらしいのです。

本書でも輿車図考からは多くの引用があります。200年の時を隔てて研究が引き継がれていくのは素敵だなと感じました。

「源氏物語」の牛車

本書の最後の部分(第六章第3節)では,源氏物語の中での牛車の描かれ方を取り上げています。筆者は「輿車図考の補遺を作成してみよう」と書いていますが,第五章まで読んで得られた知識の「おさらい」や「応用問題」にもなっていて,こんなところも上手だなあと思います。源氏物語の幾つかのシーンについて,どんな種類の車に乗り,中はどんな様子だったのか,同じ車に乗る人たちはどんな人間関係なのか,を確認していきます。

この節を読むと,本書冒頭の「古典文学を読み解くため牛車に関する知識は必須」という文章も,まあ,何となくだけど,そうかもしれないと思えてきます(出来の悪い読者でゴメンナサイ)。

また,源氏物語は,登場人物の関係性をずいぶん細かく描写しているのだなと感じました。キャラクター相互の関係性の描写を重視する点が現代の少女漫画に通じるものがあります。当時の読者にとっては少女漫画みたいな感じでとらえられていたのかもしれません。

役に立つ研究って何だろう

本書は私にとっては,とても面白かったのです。でも,このような研究は「役に立たない」という理由で研究費が切り捨てられてしまうのではないかと心配になりました。

個人的には,研究者が面白いと感じて他にやっている人が少ないテーマなら,研究する価値があると考えています。でも世間一般のお財布から出すわけですから「役に立たない」と断じられてしまったら,研究費的にはきびしいでしょう。

この「役に立つか立たないか」でお金の出し方を判断する風潮は最近,猖獗しょうけつを極めているようです。国の教育に関する審議会の委員が「二次方程式などは社会に出ても何の役にも立たないので追放すべきだ」という意見を紹介し,その後二次方程式の解の公式が中学校課程で必修から外された例もあります。国民の全てが学ぶべき中学校課程でさえ,役に立つか立たないかという個人の意見(感想?)に影響されたかもしれないのです。大学での研究に対して「役に立つか立たないか」という基準で判断が下されてしまうのは,いたしかたないのかもしれません。

でも,「失われた日常生活」を知るための研究は国にとっても役に立つ成果をもたらす,と主張したいのです。なぜかと言うと,そのような研究の成果は国民の一人一人が心を拡げることを大いに助けるからです。国民がきちんとした研究成果に基づいて広い視野で考えるようになることが都合が悪い,と考える為政者はいないはずですから,是非とも考慮してもらいたいです。(何しろ,心を拡げ空間や時間の異なる世界をイメージする能力はあの「ラーマーヤナ」の中で,人間を人間たらしめているとまで言われているのですから。)

最近は,社会的課題について論ずる際に「伝統を守れ」と主張する声をよく聞きます。その「伝統」が現代の私たちの基準で見たとき,まっとうなものなら,是非守っていくべきです。そして,その伝統がいつ生まれ,生まれた時代の人たちがどのように生活していたか,どのように伝えられてきたかを,学問的に明らかにし共有しておくことが必要なのではないでしょうか。

そして,松平定信のように源氏物語を七回も書写しろとまでは言いません。教育や研究に関するお金の出し方に関わる人たちは,せめて研究者たちの「知りたい」という気持ちを理解できる人たちであってほしいと思います。

]]>
https://tamlab.fc2.page/category-materials/1964/feed/ 0
奇跡のフォント・・・障害は人ではなく社会にある https://tamlab.fc2.page/category-materials/1811/ https://tamlab.fc2.page/category-materials/1811/#respond Mon, 29 May 2023 08:49:56 +0000 https://tamlab.fc2.page/?p=1811 高田 裕美:奇跡のフォント 教科書が読めない子どもを知って―UDデジタル教科書体 開発物語,時事通信社; New版 (2023)

UDデジタル教科書体」というフォントが世に出るまでの経緯について開発の中心となったフォントデザイナー自身が書いています。このフォントは2016年にリリースされ,Windows OSに標準でバンドルされています。

いろいろなことを考えさせられる本でした。私にはうまくまとめることができないので,(ほぼ)書いてある順に紹介していきましょう。

なお,フォント名の“UD”はUniversal Designの略語で「文化・言語・国籍・年齢・性別・能力の違いによらず,より多くの人が使えるように配慮されたデザイン(p.11)」のことです。

『これなら読める! 俺,バカじゃなかったんだ!』(p.13)

ディスレクシアと診断されている小学生の子供が,いつも使っている電子教材のフォントをUDデジタル教科書体に変えてみた際に発した言葉です。ディスレクシア(dyslexia)とは読字障害とか識字障害などと訳されていて,学習障害(Learning Difficulty, LD)の一種です。ディスレクシアの人は,知的な問題がないのに,文字を読んで意味を把握することが苦手なため学習が困難になってしまうのです。このエピソードを筆者自身がツイッターに投稿したところ2万件を超えるリツィートがあったそうです。

これをを読んだときは,フォントを変えただけで文章が読めたり読めなかったりするんだろうか? と感じました。

障害は,人ではなく,社会にある。(p.14)

最も強い印象を受けた言葉です。生きる上で困難さや不便を感じている人たちは,多くの場合,少数者・社会的な弱者であることが多いでしょう。そして,その人たちの感じる困難さは「当人の問題」として扱われ,自己責任で解決すべきものとして扱われているようです。

しかし,変えるべきは社会なのでは? 社会を構成する一人一人にも(つまり私にも)責任があるのでは? ということが問いかけられています。

私とフォント

私「ヤじるし」は,自慢じゃないですが教材のデジタル化には早くから取り組んでいた方でしょう。講義を始めたころに主流だったドットマトリックス式のプリンタは,教材作成には全く向いていないので,コピーと切り貼りというアナログな手法が資料作りの中心でした。

しかし,Apple社のMacintoshが発売されるとこれを導入し,Adobe社のアウトラインフォントとレーザプリンタを使ったDTP(Desk Top Publishing)で配布資料を作成できるようになりました。これでフォントと作れる資料の選択肢がぐっと広がりました。(沖電気さんから発売されたばかりのLEDプリンタを寄付していただいたのが,とても助かりました。遅くなりましたが,感謝いたします。ありがとうございます。)

講義中に使う視覚教材は,配布資料よりもデジタル化が少し遅れ,レーザプリンタで作ったOHP(オーバヘッドプロジェクタ)の時代が続きました。その後,液晶プロジェクタが出始めたので,やっとPowerpointを使った現在の方式に移行できました。

私の作成したPPTの資料では「板書」として受講者に書き写してもらう文章のフォントを手書き風のものに変えてあります。これは,書き写す部分を区別しやすくするためですが,もう一つ狙いがありました。

それは,「書き写す文字を少し読みにくくすると内容の定着率が向上する」という研究結果についての記事を読んだことがあるからです。ただし,この研究結果の追試や再現性についての検証はしていません。学生の反応は「読みにくい」という指摘が若干ありましたが,板書を書き写す時間を十分取ることで特に問題にはならなかった,と理解しています。しかし,今回,本書を読んでから,何割かの学生さんには苦痛を与えてしまったかもしれないと反省しています。

PPTスライドで使っているフォントの例 (「基本的考え方」をUDデジタル教科書体で表記)

フォントを作るのは大変だ

PPTなど講義資料の手書き風フォントには,主に「あくあフォント」を使っています。趣味で作成されたフォントで今は開発は停止していると思いますが,非常に多くの場面で使わせていただきました。作者の方には,この場を借りて御礼を申し上げます。

私自身の手書き文字のフォント化も試みたのですが,すぐに諦めました。文字数が多すぎるのです。それでフォント作りは大変なんだろうと漠然と思っていました。しかし本書を読むと,フォント作りが物凄く大変なことがわかりました。文字数の多さだけが問題ではないのです。私のような根性無しにはとても務まるものではないです。

例えば写植用のフォント原図では,錯視で斜めに見えることを見越して,横線の角度を水平から少しずらしています。ところがこれをデジタルフォント化すると,表示や印刷のためラスタライズするときにジャギーが目立ってしまうのです。(写植の前から使われていた活版印刷の文字原盤はインク面を紙に押し当てた際の滲みを考慮して少し細く作ってあるので,そのままでフォントにすると細く見えてしまうという問題もあったそうです。)

その他,明朝体の文字の止めのところに置く三角形の“ウロコ”の大きさのことなど,考慮すべきポイントが非常に多いのです。とにかく,髪の毛一本の幅や高さの違いでも,バランスが崩れて見えるというのです。

フォントの部位の名称

このためフォントデザイナーである筆者は,文字パーツの髪の毛一本の幅や位置の違いにも敏感になり,小説を読んでも気になるところが多すぎて内容が頭に入らなくなってしまうことがあるそうです。(ひょっとして,ディスレクシアの方が読んだ文字を理解できなくなるときは,こんな感じなのかもしれない,と思いました。)

教科書体・・・文字の書き方を教えるためのフォント

さらにさらに,UDデジタル教科書体フォントは文字の書き方を教え学ぶために使うことを目的としていて,これがまた問題を難しくしています。文字の書き方を学ぶためには,止めや払いなどがわかるように,フォントの太さを変化させる必要があります。ところが,このような文字幅の変化はジャギーの原因になります。しかも,ディスレクシアの人達の中には文字の端が尖っていることが気になって文字から言葉・文章への変換がうまくできない人がいるというのです。

資料作成のためいろいろなフォントを試していたとき「教科書体」というフォントの一群があることを知りました。でも「何に使うのかわからないけれど,あまり面白みのないフォントだなあ」としか感じなかったのです。しかし,児童に文字の書き方を教える現場では欠かせないものらしいのです。肢体不自由の児童も,下の図のような,手の動きまで表現までした書体を使って「ストーリー仕立て」で説明すると覚えてくれやすいそうです。

教科書体を使って文字を教える 本書p.131の図を模写 

暗黒の時代

筆者がフォントメーカーの株式会社タイプバンクに就職した1984年は,私が職を得た年でもあります。年齢は私の方が上なのですが,社会に出てからは同じ時代を生きてきたといってよいでしょう。ですから,本書に書かれている内容の技術的背景や時代の雰囲気は,読んでいてとても他人事ひとごととは思えないのです(錯覚かもしれません,たぶん思い込みもあるでしょう)。

タイプバンク社の経営は立ちいかなくなって社員全員が解雇され,筆者は株式会社モリサワに移籍します。多数の社員を抱える企業では,効率や開発コストの優先度は高くなるのでしょう。モリサワでは,UDデジタル教科書体フォントは教育関係者に高く評価されていたものの少数者しか必要としないニッチな商品として扱われていたようです。このため,フォントのデザインの細かなところまでこだわる筆者の立場は,だんだん苦しくなってきます。

この,筆者が「暗黒の時代」と呼んだ時期を書いた部分は,読んでいてつらく,息ができないような気持ちになりました。

逆転

しかし,筆者の仕事を見守ってくれている人もいたのです。その一人であるモリサワの常務取締役を務めていた方に筆者が直談判したことから事態は動き始めます。そして2016年6月にUDデジタル教科書体がリリースされることになります。

高く評価されていたフォントであることや筆者の熱意や努力を知っている人がいたことが結果につながったことは確かでしょう。でもそれだけでは不十分で,障害者差別解消法の施行が2016年だったことが大きかったようです。この法律により「公共の場での合理的な配慮の提供」が官公庁や役所,学校の現場で求められるようになったのです。

私はここから,ビジネスで社会的課題にアプローチすることの重要性を学びました。例え利用シーンやターゲットが限られていても,それが社会的課題を解決に導くものであれば必ず反響はある。(p.154)

企業や大学の役割

UDデジタル教科書体の開発では,大学の研究者も大きな役割を果たしています。 

「障害者支援」に参入してくる会社や人は多いけれど,地道な努力が必要な割に利益にならない実情を知ると投げ出してしまうそうです。UDと名乗る以上は「誰でも使える」ことのエビデンスを作るための手間のかかる作業が不可欠になります。コストのかかる検証作業にマンパワーを割くことは企業としては難しいでしょうし,開発した製品を使って利益を得る企業だけで検証をすることには問題があるかもしれません。企業から独立した立場の大学の研究者が貢献できることは多いはずです。

社会的課題の解決を目指す製品は,社会的責任を自覚した企業人だけではなく大学の研究者が果たす役割が大きくなるだろうと感じました。

本当の多様性って何だろう (p.177)

UDデジタル教科書体フォントは読みにくい,好きじゃない,と感じる人もいるそうです。選択できるようにしておけば良いのでは?と思って読んでいくと,筆者は,それでは互いの多様性を認め合う社会になるのだろうかと述べ,次のような言葉を投げかけてきます。

「自分はこれが好きだから,ほかの人が何を選んでも関係ない」

「あなたが何を好んで使いやすいと思うのは勝手だけど,自分の知ったことではない」

「あなたのことには口をださないから,自分のことも何も言うな」

 これは本当に多様性でしょうか。

 互いに認め合っている社会だと言えるでしょうか。

 もし全員がそうなれば,少数派が抱えている困難は,いつまでたっても社会に理解されることも,改善されることもないでしょう。 

 むしろ「多様性」という言葉が,弱者への無関心を肯定する便利な隠れみのになってしまうのではないか。 (p.179)

みんながやりたいことを勝手にやればよい」という状況は多様性の尊重にはつながらないということでしょう。みんなが勝手にすれば,いつまでも弱い人は弱いままです。弱い立場にある人達が少数であっても,不便だ不公平だと感じていることを口にだせるようにすることが第一歩で,好き嫌いは別にして,他人の口や自分の耳を塞ぐことはせず実態を知ることから始めなければならない,ということでしょう。

(私の)課題

まとまりのない文章になってしまいました。以下は,私が感じた課題の一部です。

  • 読みにくさ/読みやすさを決めている認知的な仕組みは?
    UDディジタル教科書体フォントがロービジョンやディスレクシアの人たちにとって読みやすいフォントであることは,他のフォントとの理解度の比較テストなどで定量的な検証がされています。しかし「なぜ,このフォントだと文字列を文章として理解できるのか(あるいは,できないのか)」を人間の認知の仕組みまで考慮して明らかにされてはいないようです。読みにくさ/読みやすさに人間の認知がどのように関わっているかという仕組みがわかれば,より有用な対応ができるような気がします。
  • 大学の教育ではどうだろうか
    在職中には,数式を見てもすぐに意味が理解できない学生が結構多いと感じていました。数式のフォントが関係しているのかもしれません。
    板書をノートに書き写す時間も学生によって大きな差があります。(全員が書き写してから次に進むようにしていましたが,速く写せる学生には不満を持つ人もいました。)
    上に述べたような差異が認知システムの特性が原因で生じているとすれば,問題を抱えている学生に「やる気を出せ」,「練習の時間を増やせ」というだけでは効果が少ないことになります。また,このような学生は,自己責任で何とかしようと考えたり,わからないことを言い出せなかったりしているのかもしれません。
  • 少数派への配慮は多数派の不利益か?
    一人でも不利益にならないようにしようと思っても,技術や施策による問題解決には,最後はコストの壁が立ちはだかります。少数派や弱者に配慮した社会を作ることは多数派にとって不利益になり,それは全体にとって不利益になると感じる人が多いかもしれません。
    コストや,特性の異なる集団間の対立が関係してくるので,すぐには正解の見いだせない課題です。

上に述べた3つ目の課題に関しては,p.154でも述べられているように「少数者に配慮した技術や施策は,一見コスパが悪いように見えても,社会的な課題を解決するなら全体の利益になる」と考えています。ポイントは「社会的な課題の解決」ですね。

また,「多数派」とみなされている集団の中にも,自分が当然受けるべき利益を受けていないという不満や不安を感じている人たち(実は弱い立場の「隠れ少数派」?)もいるはずです。そのような人たちへの配慮も必要になるでしょう。

なお「少数者への配慮が全体の利益につながる」と書きましたが,個人的にはもう少し悲観的で,「人を大事にしない社会システムをそのままにしておいたら,少数派も多数派もいずれはひどい目に合う」と心配しています。この辺りの話に関連して「確率的因果応報説」とでもいうべき仮説を持っているのですが,それを紹介するのは別の機会にしましょう。

]]>
https://tamlab.fc2.page/category-materials/1811/feed/ 0
PPTファイルに音声を自動で埋め込む その6 配布用実行ファイル https://tamlab.fc2.page/category-materials/1730/ https://tamlab.fc2.page/category-materials/1730/#respond Sun, 07 May 2023 01:31:27 +0000 https://tamlab.fc2.page/?p=1730 pptファイルにオーディオデータを埋め込むアプリケーションソフトに機能を追加し,実行ファイルを作成しました。追加した機能は,その4で「今後の予定」として挙げていた4つの機能のうちの,自動アニメーションへのオーディオデータの付加,スクリプト作成補助の機能pptのノート機能の利用,の3つです。

作成したアプリの動作中のGUI画面

これらの機能を追加するのは後回しでもよいと思っていたのですが,結局全部を付け加えてしまいました。考えてみたら,使う上では必要だったからです。

とりあえず一応の機能がそろったので,本来やりたかった「講義資料の整理」の作業に使うための実行ファイルも作成しました。こうしておくと,Pythonをインストールしていない環境でも使うことができます。個人的な事情になりますが,VSCodeを立ち上げていると,ついコードをいじりたくなって時間がつぶれてしまうのですが,それを防ぐこともできます。

実行ファイル

Windows用の実行ファイルと動作検証用のpptxファイルなどをまとめたzipフォルダは,以下のリンクからダウンロードできます。

フォルダの内容

解凍したフォルダはAddAudio2PPTX_testという名前です。下の図のように2つのフォルダ,AddAudio2PPTX,testが入っています。

AddAudio2PPTX_testの内容

AddAudio2PPTXフォルダの中には2つのファイル,AddAudio2PPTX.exeとconfig.iniがあります。

testフォルダには動作確認用のデータが入っています。

    AddAudio2PPTXフォルダの内容               testフォルダの内容

ナレーションフォルダtestの内容

詳しく説明します。

  • AddAudio2PPTXフォルダ
    実行ファイルと設定ファイルが格納されている。
    • AddAudio2PPTX.exe
      実行ファイル
    • config.ini
      設定ファイル。実行ファイルと同じディレクトリに配置する。
  • testフォルダ
    動作確認用のデータとして,以下のファイルとフォルダが格納されている。
    • test.pptx
      動作確認用pptxファイル。アニメーションは設定済み。ノートのテキストは空白になっている。
    • test.txt
      スクリプトファイル。
    • test(音声ファイルのフォルダ)
      音声ファイルと対応するテキストファイルが格納されている。テキストファイルは「かんたん!AITalk 5」が合成の際に自動的に生成したもので,現時点では利用していない。

動作確認をする場合は以下の点に注意してください。

  • 実行ファイルはWindows 11(64bit)環境で作成
  • 音声合成ソフトは「かんたん!AITalk 5」を使用
    「かんたん!AITalk 5」を持っていない場合は,音声合成ソフトの制御機能は使えない。
  • GUI制御をする場合の注意
    「かんたん!AITalk 5」を制御するときは,他のソフトでの作業はしないこと。他のソフトのウインドウにマウスクリックやホットキーの信号が送られ,編集中のファイルなどに予期しない影響を与える恐れがある。
  • 自作のpptxファイルで動作確認する場合はバックアップを取っておく
    ファイル名の設定によっては,大事なデータが上書きされてしまうかもしれない。(コード上は,その可能性はないと考えているが,見落としがあるかもしれない。)
  • 予期せぬ挙動に注意
    「アプリ」としての完成度は低い。例えば,存在しないファイルやディレクトリを対象として設定した場合の挙動など,起こり得るすべての状況を想定した確認はしていない。

動作確認の手順

テスト用のデータに対する処理を例に,使い方を説明します。

  • ⅰ)実行ファイルと設定ファイルの配置
    • AddAudio2PPTXフォルダに入っているAddAudio2PPTX.exeとconfig.iniを,ローカルディスクの適当な場所に配置する。2つのファイルが同じディレクトリ階層にあればよい。
      ただし,C:\Program Filesフォルダの中に入れるのは避けた方がよいだろう。ここに配置して動作させたところGUI画面からアプリを終了できなくなった。
  • ⅱ)動作確認用データの配置
    • testフォルダごと,適当な場所に配置する
  • ⅲ)起動 
    • 起動:AddAudio2PPTX.exeのアイコンをダブルクリックして起動する。

起動画面

メニューバー > ツール > AITalk 5の実行ファイルのパス を起動

  •  「かんたん!AITalk 5」の実行ファイルのパスを指定(初回のみ)
    • 「かんたん!AITalk 5」を使って音声合成をする場合は,初めての起動の際にAITalkの実行ファイルのパスを指定する。通常のインストールでは,パスは
      C:/Program Files/AI/KantanAITalk/KantanAITalkEditor/KantanAITalk.exe
      となっている。
      操作ウインドウの上部のメニューバー > ツール (図の⓪)から“AITalk 5の実行ファイルのパス”を起動することにより,ファイル指定のダイアログボックスが開くので,KantanAITalk.exeを探して選択し,「開く」ボタンをクリックする。
  • ⅳ)ファイルとフォルダのパスをセット
    • オーディオデータを埋め込む対象となるpptxファイル(①)オーディオデータのファイルを格納したナレーションフォルダ(②)プレゼンテーションの台本を記述したスクリプトファイル(③),の3つのパスを,それぞれの“Set”ボタンをクリックして設定する。個別にセットする場合は,Autoチェックボタン④のチェックを外しておく。
      ①,②,③の3つが同じフォルダの同じ階層に格納されていて,しかも拡張子を除いた名称が同じになっている場合は,④にチェックを入れておき,①のpptxファイルのパスだけを設定すれば,②と③は自動でセットされる。(testフォルダをそのまま使う場合は,この条件を満たしている。)
  • ⅴ)オーディオデータの付加
    • “Add audio”ボタン(⑤)をクリックすると,ナレーションフォルダ内のwav形式ファイルがスクリプトに記述された指示に従ってpptxファイルに埋め込まれる。埋め込まれたファイルは,対象としたpptxファイルの名前に“_A”をつけた新しいファイルとしてフォルダ内に生成される。元のpptxファイルは変更されない。
  • ⅵ)オーディオデータの新規作成(「かんたん!AITalk 5」をインストールしている場合のみ)
    • “Make new audio”ボタン(⑥)をクリックすると,オーディオデータの新規作成が開始される。作成されたデータは,ナレーションフォルダの内容を全て削除してから書き込まれる。
  • ⅶ)作成されたファイル/フォルダの確認
    • zip形式で配布した動作確認用ファイルをそのまま使用した場合,フォルダの中には新たに以下のファイルとフォルダが作成される。
      • test_A.pptx:オーディオデータを埋め込んだpptxファイル
      • test_zip:test_A.pptxに圧縮する前のフォルダ
        この中身を調べると,pptxファイルの内部構造がわかる。学習用に,削除せず残すようなコードにしてある。

オーディオ付加の処理後のtestフォルダの内容

補助用の機能

アプリには基本的な機能に加え,作業の補助用に以下の機能を追加しています。

スクリプトの作成・編集の補助

  • pptxファイルからスクリプトのスケルトンを作成
    • アニメーションを設定したpptxファイルからアニメーションの情報を読み取り,スクリプトのスケルトン(骨組み)を作成する。スケルトンにナレーションのテキストを追加することで,スクリプトを生成できる。
    • メニュー > ファイル > pptxファイルからスクリプトのスケルトンを作成
      により実行される。ファイル名を指定可能。
  • スクリプトをpptxのノートにコピー
    • スクリプトをpptxファイル内のノートのテキストブロックに書き込む。一度,書込んでおくと,次からはpptアプリのみでスクリプトの編集が可能になる。
    • メニュー > ファイル > スクリプトをノートに上書き
      により実行される。元のpptxファイルのノートのテキストブロックにスクリプトが書き込まれる。
  • pptxのノートからスクリプトファイルを作成
    • pptxファイル内のノートのテキストブロックに書かれたスクリプトからスクリプトファイルを作成する。
    • メニュー > ファイル > ノートからスクリプトを作る
      により実行される。作られるスクリプトのファイル名を指定できる。

  スクリプトのスケルトン         ナレーションを付け加えたスクリプト

スクリプトを自動で書き込んだ後のpptxノート

これらの補助機能を使えば,pptxファイルのノートに書いたスクリプトからオーディオデータの自動埋め込みが可能になります。pptxのノートを利用することで,メモ帳などのテキスト編集ソフトが不要になり,スライドの順番の変更も容易になります。(詳しいことはコードの解説のところで触れますが,スクリプトはスライド番号を指定しなくても動作するようにできます。)

その他

動作状態を表示するため以下のようにしています。

  • データ処理中の表示
    “Add audio”と“Make new audio”のボタンはクリックして処理を開始すると,処理が終了するまでボタンの色が赤く変わるようにしている。
  • 処理時間の表示
    ステータス等の表示用にテキストウインドウを追加した。現在,オーディオデータ付加と新規オーディオ作成の処理の終了時に,消費時間を表示するようになっている。

コードについて

アプリのPythonコードの解説については,現在,投稿の準備中です。読んだ人が同じ機能のシステムを再現できるだけでなく,読んだ人の頭の中で「機能する知識」を再現できることを目指したいのですが,心もとない状況です。(そもそも読む人がいるのか,という問題もありますが・・・。)

]]>
https://tamlab.fc2.page/category-materials/1730/feed/ 0
PPTファイルに音声を自動で埋め込む その5 Pythonによる音声合成ソフトの制御 https://tamlab.fc2.page/category-materials/1676/ https://tamlab.fc2.page/category-materials/1676/#respond Sun, 23 Apr 2023 23:04:27 +0000 https://tamlab.fc2.page/?p=1676 pptファイルにオーディオデータを埋め込むアプリケーションソフトに,音声合成ソフトのGUIを操作するコードを追加しました。pptxフォーマットのファイルにオーディオデータを埋め込むコードはPythonで作成し,GUI操作で実行するものに比べ圧倒的に高速であることがわかりました。次のステップとしては,pptxファイルからアニメーション起動の情報を読み取るコードを作成するつもりでした。しかし予定を変更し,音声合成ソフトのGUIを操作するコードを追加することにしました。音声合成ソフトは株式会社エーアイさんの「かんたん!AITalk®5」(以下,AITalkと記します)にアップデートしました。動作確認できたので,紹介します。

<詳しく読む>

PPTファイルに音声を自動で埋め込む その4の末尾に書いたように,音声合成ソフトを制御する機能の優先度は低いと考えていました。音声合成ソフトのGUIを操作してオーディオデータを生成して保存するので,Pythonでコードを作ったからといって速くなるわけではないからです。(GUIを使わないで操作できる製品群も提供されていますが,お値段の関係で手が出ません。)

それで,PythonでGUIを制御するコードへの移行は(興味があるのですが)後回しにしていました。しかし,ちょっと事情が変わってきました。音声合成ソフトを変更することになったからです。

実は,作業をしていたPCのDドライブが壊れてしまったのです。DドライブにはPythonの開発環境やソースコード,音声合成ソフトの「かんたん!AITalk®3」が入っていました。幸い,Pythonのソースコードはバックアップを取っていましたし「かんたん!AITalk®3」は再インストール可能です。しかし自分でイントネーションなどを調整して登録してきた辞書データは全部消えてしまいました。

ソフトの再イストールのため株式会社エーアイさんのサイトにアクセスしたところ後継ソフトの「かんたん!AITalk®5」にアップデートできるということがわかり,それをインストールすることにしました。

「かんたん!AITalk®5」と「かんたん!AITalk®3」ではユーザインタフェースが若干異なっているので,AHKスクリプトを直す必要があります。それならPythonで作り直してしまおうということになりました。それに,オーディオデータ作成の制御と埋め込みのコードは1つのアプリケーションソフトとしてまとまっていた方が便利だからです。

<閉じる>

アプリの概要

下がアプリの操作画面です。“Add new audio”というラベルのボタンが1つ増えています。

作成したアプリの操作画面

このボタンをクリックすると,音声合成の操作を開始します。”Script File:”のテキストボックスで設定さたテキスト形式のスクリプトファイルを読み込み,中に書かれているナレーションのテキストをAITalkのテキストボックスにコピーして音声合成します。得られたwav形式のオーディオファイルは,格納するためのフォルダ(Narration Folder:で指定する)に書き込みます。このフォルダの内容は,音声合成の前に全て削除されます。

全体の処理の流れ

GUIの外観はあまり変わっていないのですが,アプリケーションの内部の構造に手を加えました。初めにAHKで作成したコードは,スクリプトの指示に従って音声を合成してファイルを保存するもので,「スクリプトに従って働く仮想機械」をイメージして設計しています。スクリプトファイルのテキストを1行ずつ読み込んで保存するwavファイルの名前を決め,音声合成ソフトのGUIを制御しています。このコードのGUI制御部分をPowerpoint用に変更して,wav形式のデータをpptに埋め込むAHKコードを作りました。

Pythonで作成したオーディオデータ埋め込みソフトの設計は,この初めのイメージに引きずられています。しかし,このソフトに音声合成ソフトの制御コードをそのまま追加すると,コードの重複した部分が多くなり,拡張性やメンテナンスの点で問題があります。

そこで,機能追加をしやすいようにコードを少し整理しました。下に,全体の処理の流れの概念図を示します。(この図は,コード全体の機能を説明するためのものです。実際のコードの構造を示しているものではありません。例えば,read_script_file()関数は,make_audio_files()関数などの内部で呼び出されますが,独立したブロックとして描いています。)

アプリの処理の流れ

コードは以下のような機能の部分に分かれています。

  • main
     GUIのボタンやテキストボックスなどを生成し,ボタンをクリックすると予め定められた関数を呼び出す。
  • pptxファイルの処理とAITalkの制御をする関数群
    mainから呼び出され,zip解凍したpptxファイルにオーディオデータを埋め込んだり,AITalkのGUIを制御したりする複数の関数がある。
  • スクリプトの解析部分
    スクリプトの書かれたテキストファイルを読み込んで,pptxファイルの処理やAITalkの制御をするために必要なパラメータのリストを作成する。パラメータは,現時点では,アニメーションの番号(スライド番号,クリック番号,自動アニメーション番号),wavファイルのパス,ナレーションフォルダのパス。各関数は,このリストの順に処理やGUI制御を実行していく。

mainルーチンと関数の間のデータのやりとりは,基本的には関数の引数をつかっています。図の中で“p_info”と書いてあるのは,関連する複数のファイルやフォルダのパスの情報をまとめたクラスのインスタンスです。クラスといっても,C言語で複数の変数やパラメータをまとめて扱うのに使う「構造体」のような使い方しかしていません。また,このインスタンスp_infoはグローバル宣言して使っています。

関数間のデータの共有の方法については,今後,変更する可能性があります。

コードの説明

AITalkのGUIを操作する関数とその周辺を中心に説明していきます。コードの全部を掲載すると長くなるので,抜粋したもので説明します。

メイン関数

メイン部分のコードは以下のようになります。

# Main
root = tk.Tk()
root.title('AddAudio2pptx')
root.geometry("940x680")
frame = tk.Frame(root)
pyautogui.FAILSAFE = False

# Get Working Directory
wd = os.getcwd()
# Arrangeent of Widget
fonts = ("", 14)
fonts_b = ("", 11)
b_width = 60
pptx_lb = tk.Label(text='PPTX File Name:', font = fonts)
pptx_lb.grid(row = 1, column = 0, padx=10, pady=10,sticky=tk.W)
pptx_box = tk.Entry(width = b_width, font = fonts_b)
pptx_box.grid(row = 1, column = 1, padx=10, pady=10,sticky=tk.W)
pptx_btn = tk.Button(root,text ="Set",command =set_pptx_file,font =fonts)
pptx_btn.grid(row = 1, column = 2, padx=10, pady=10,sticky=tk.W)

chk_st = tk.BooleanVar()
chk_st.set(True)
chk = tk.Checkbutton(variable=chk_st, text='Auto Set')
chk.grid(row = 1, column = 3, padx=10, pady=10,sticky=tk.W)

dir_lb = tk.Label(text='Narration Folder:', font = fonts)
dir_lb.grid(row = 2, column = 0, padx=10, pady=10,sticky=tk.W)
dir_box = tk.Entry(width = b_width, font = fonts_b)
dir_box.grid(row = 2, column = 1, padx=10, pady=10,sticky=tk.W)
dir_btn = tk.Button(root,text ="Set",command =set_narration_folder,font =fonts)
dir_btn.grid(row = 2, column = 2, padx=10, pady=10, sticky=tk.W)

scpt_lb = tk.Label(text='Script File:', font = fonts)
scpt_lb.grid(row = 3, column = 0, padx=10, pady=10,sticky=tk.W)
scpt_box = tk.Entry(width=b_width, font = fonts_b)
scpt_box.grid(row = 3, column = 1, padx=10, pady=10,sticky=tk.W)
scpt_btn = tk.Button(root,text ="Set",command =set_script_file,font =fonts)
scpt_btn.grid(row = 3, column = 2,padx=10, pady=10, sticky=tk.W)

text_box = tk.Text(width=80, height = 20, font = fonts_b)
text_box.grid(row = 4, column = 0, columnspan = 4, padx=10, pady=10,sticky=tk.W)
xbar = tk.Scrollbar( orient = 'horizontal' )
ybar = tk.Scrollbar( orient = 'vertical' )
xbar.grid(row = 5, column = 0, columnspan =4, padx=10, pady=10,sticky=tk.W+tk.E)
ybar.grid(row = 4, column = 3, padx=10, pady=10,sticky=tk.W)
text_box[ 'xscrollcommand' ] = xbar.set
text_box[ 'yscrollcommand' ] = ybar.set
xbar[ 'command' ] = text_box.xview
ybar[ 'command' ] = text_box.yview

make_audio_btn = tk.Button(root,text ="Make new audio",command =run_make_audio, font =fonts)
make_audio_btn.grid(row = 6, column = 0,padx=10, pady=10, sticky=tk.W)

exe_btn = tk.Button(root,text ="Add audio",command =run_add_audio_files, font =fonts)
exe_btn.grid(pyautogui.FAILSAFE = False = 6, column = 1,padx=10, pady=10, sticky=tk.W)  
root.mainloop()

マウスポインタの位置が限界を超えたとき

pyautogui.FAILSAFE = False

GUIの制御中にマウスポインタの位置が限界を超えてエラーにることを防ぐため,このような設定にしてある。

ファイル/フォルダを設定するボタン

pptx_lb = tk.Label(text='PPTX File Name:', font = fonts)
pptx_lb.grid(row = 1, column = 0, padx=10, pady=10,sticky=tk.W)
pptx_box = tk.Entry(width = b_width, font = fonts_b)
pptx_box.grid(row = 1, column = 1, padx=10, pady=10,sticky=tk.W)
pptx_btn = tk.Button(root,text ="Set",command =set_pptx_file,font =fonts)
pptx_btn.grid(row = 1, column = 2, padx=10, pady=10,sticky=tk.W)

pptxファイルのパスを設定するためのボタンを生成しているコードです。

  • 表示のためのテキストボックスを生成
    pptx_box = tk.Entry(width = b_width, font = fonts_b)
    tkinterのEntryボックスを設定したpptxファイルのファイル名を表示するために使っている。
  • ボタンを生成し,クリック時に呼び出される関数を設定
    pptx_btn = tk.Button(root,text =”Set”,command =set_pptx_file,font =fonts)
    “Set”と表示されたボタンを生成する。このボタンをクリックすると,set_pptx_file()という関数が呼び出される。
chk_st = tk.BooleanVar()
chk_st.set(True)
chk = tk.Checkbutton(variable=chk_st, text='Auto Set')
chk.grid(row = 1, column = 3, padx=10, pady=10,sticky=tk.W)

チェックボックスと関連する変数を生成しています。

  • チェックボックスで設定するブール変数を定義
    chk_st = tk.BooleanVar()  # chk_stというブール型変数を定義
    chk_st.set(True) # その値をTrueに設定
    chk = tk.Checkbutton(variable=chk_st, text=’Auto Set’)
    # “Auto Set”というラベルをつけたチェックボックスを作り,その値と変数chk_stを関連づけ

このチェックボックスにチェックマークを入れておくと,標準的なファイル名とフォルダ名に従ったディレクトリ構成にしてある場合は,pptxファイルを設定するだけでナレーションのフォルダとスクリプトファイルは自動的に設定されるようにしています。

dir_lb = tk.Label(text='Narration Folder:', font = fonts)
dir_lb.grid(row = 2, column = 0, padx=10, pady=10,sticky=tk.W)
dir_box = tk.Entry(width = b_width, font = fonts_b)
dir_box.grid(row = 2, column = 1, padx=10, pady=10,sticky=tk.W)
dir_btn = tk.Button(root,text ="Set",command =set_narration_folder,font =fonts)
dir_btn.grid(row = 2, column = 2, padx=10, pady=10, sticky=tk.W)

ナレーションのフォルダを設定するコードです。”Set”ボタンのクリックでet_narration_folder()が呼び出されます。

scpt_lb = tk.Label(text='Script File:', font = fonts)
scpt_lb.grid(row = 3, column = 0, padx=10, pady=10,sticky=tk.W)
scpt_box = tk.Entry(width=b_width, font = fonts_b)
scpt_box.grid(row = 3, column = 1, padx=10, pady=10,sticky=tk.W)
scpt_btn = tk.Button(root,text ="Set",command =set_script_file,font =fonts)
scpt_btn.grid(row = 3, column = 2,padx=10, pady=10, sticky=tk.W)

スクリプトファイルのパスを設定するためのボタンです。set_script_file()という関数を呼び出すために使います。

スクリプトを表示するテキストボックス

text_box = tk.Text(width=80, height = 20, font = fonts_b)
text_box.grid(row = 4, column = 0, columnspan = 4, padx=10, pady=10,sticky=tk.W)
xbar = tk.Scrollbar( orient = 'horizontal' )
ybar = tk.Scrollbar( orient = 'vertical' )
xbar.grid(row = 5, column = 0, columnspan =4, padx=10, pady=10,sticky=tk.W+tk.E)
ybar.grid(row = 4, column = 3, padx=10, pady=10,sticky=tk.W)
text_box[ 'xscrollcommand' ] = xbar.set
text_box[ 'yscrollcommand' ] = ybar.set
xbar[ 'command' ] = text_box.xview
ybar[ 'command' ] = text_box.yview

スクリプトファイルをセットすると,その内容を読み込んで表示します。現在のところは表示だけで,編集などの機能はありません。またスクロールバーをつけるようにコーディングしたつもりですが,機能していないようです。

AITalkのGUIを制御する関数

“Make new audio”ボタンをクリックすると呼び出される関数make_audio_files(p_info)は,AITalkのGUIを制御してwav形式のオーディオファイルを作って指定されたフォルダに格納します。関数全体の記述を示します。

def make_audio_files(p_info): 
    if os.path.isdir(p_info.narr):
        shutil.rmtree(p_info.narr)
    os.mkdir(p_info.narr)
    ai_path = ('D:\Program Files\AI\KantanAITalk'
              '\KantanAITalkEditor\KantanAITalk.exe')
    ai_title = 'かんたん! AITalk 5' # AITalk 5 window tytle
    ai_title_a = 'かんたん! AITalk 5 *' # AITalk 5 window alt. tytle
    hwnd = win32gui.FindWindow(None, ai_title)
    if hwnd == 0:
        hwnd = win32gui.FindWindow(None, ai_title_a)
        if hwnd == 0:
            process = subprocess.Popen(ai_path)
            wait_until_state(ai_title, 20, True)
            hwnd = win32gui.FindWindow(None, ai_title)
    # Open and load the slideshow script file
    pptx_path = p_info.pptx

    anim_num = AnimNum()   # Numbers related to animations.
    anim_num.audio = 0 # Index of audio files to be written to ppt/media/.
    anim_num.sld = 1 # Initial value of slide number.  
 
    ani_list = []
    wav_list = []
    nar_list = []
    (ani_list, wav_list, nar_list) = read_script_file(p_info)
    list_len = len(ani_list)
    for i in range(list_len):
        anim_num = ani_list[i]
        wav_file_path = wav_list[i] 
        wav_dir = os.path.dirname(wav_file_path)
        wav_file = os.path.basename(wav_file_path)
        nar_text = nar_list[i] 

        pyperclip.copy(nar_text)  # Copy narration text to clipboard.
        forefront_window(hwnd)    # Set AITalk window to the forefront
        pyautogui.hotkey('ctrl', 'a') # Select all text.
        time.sleep(0.2)
        pyautogui.hotkey('ctrl', 'v') # Paste the clipboard contents into the text box.
        pyautogui.hotkey('ctrl', 'alt', 'S') # Synthesis and save command.
        time.sleep(1)        
        hwnd_save = win32gui.FindWindow(None, '名前を付けて保存') # Detect Dialog Box. 
        if i==0:                             # For the first time,
            narr_dir_set(hwnd_save, wav_dir) # set directory name.
        forefront_window(hwnd_save)
        pyautogui.press('tab')       # To ensure the next Alt+n works. 
        pyautogui.hotkey('alt', 'n') # Press Alt+n to set save file name.
        # pyautogui.hotkey('ctrl', 'a')
        # time.sleep(0.5)
        pyperclip.copy(wav_file)     # Copy the file name to the clipboard.
        pyautogui.hotkey('ctrl', 'v')# Paste the clipboard contents into the text box.
        # pyautogui.press('enter')
        time.sleep(0.3)
        pyautogui.hotkey('alt', 's') # To activate the Save Button.
        wait_until_state('情報', 20, True) # Wait until the End of Operation.
        pyautogui.press('enter') # Press Enter to close the window.
    return  

以下,コードの上から順に説明していきます。

関数の引数

def make_audio_files(p_info): 

引数はPathInfoクラスのインスタンス,p_infoです。呼び出し側で以下のようにして定義しています。

p_info = "global_var"
class PathInfo:
    def __init__(self):
        self.pptx = ''   # pptx file path.
        self.narr = ''   # Narration folder path.
        self.scpt = ''   # Script file path.
        self.idir = ''   # Initial directory.
p_info = PathInfo()   
  • グローバルとして使う
    p_info = “global_var”
  • ファイルパスをまとめて扱うクラスを定義
    self.pptx = ” # pptx file path. pptxファイルのフルパス
    self.narr = ” # Narration folder path.  wavファイルを格納するフォルダのフルパス
    self.scpt = ” # Script file path.     スクリプトファイルのフルパス
    self.idir = ” # Initial directory.     ディレクトリ指定の基点となるパス
  • インスタンスを生成
    p_info = PathInfo()

ナレーションフォルダをクリア

関数が呼び出されると,まずナレーションのwavファイルを格納するフォルダの中身を削除します。実際には,p_info.narrで指定されたフォルダが存在しているか確認し,存在すればフォルダごと削除し,その後で同じ名前のフォルダを作っています。

    if os.path.isdir(p_info.narr):
        shutil.rmtree(p_info.narr)
    os.mkdir(p_info.narr)
  • ディレクトリが存在する場合は削除
    if os.path.isdir(p_info.narr):
    shutil.rmtree(p_info.narr)
  • ディレクトリを新たに作成
    os.mkdir(p_info.narr)

AITalkの起動

次に,AITalkが立ち上がっているかどうか確認します。立ち上がっていない場合は起動します。

かんたん!AITalk 5の操作ウインドウ

    ai_path = ('D:\Program Files\AI\KantanAITalk'
              '\KantanAITalkEditor\KantanAITalk.exe')
    ai_title = 'かんたん! AITalk 5' # AITalk 5 window tytle
    ai_title_a = 'かんたん! AITalk 5 *' # AITalk 5 window alt. tytle
    hwnd = win32gui.FindWindow(None, ai_title)
    if hwnd == 0:
        hwnd = win32gui.FindWindow(None, ai_title_a)
        if hwnd == 0:
            process = subprocess.Popen(ai_path)
            wait_until_state(ai_title, 20, True)
            hwnd = win32gui.FindWindow(None, ai_title)
  • AITalkの実行形式のパスを設定
    ai_path = (‘D:\Program Files\AI\KantanAITalk’
    ‘\KantanAITalkEditor\KantanAITalk.exe’)
    ai_pathにパスの文字列を代入している。括弧でくくることで,文字列を複数行にわたって記述しても改行が挿入されないようにしている。
  • 起動したときのウインドウのタイトルを設定
    ai_title = ‘かんたん! AITalk 5’ # AITalk 5 window tytle
    ai_title_a = ‘かんたん! AITalk 5 *’ # AITalk 5 window alt. tytle
    AITalkの起動直後のタイトルは”かんたん! AITalk 5′”となっています。編集内容が保存されていない場合は,”かんたん! AITalk 5 *”となっています。この2つのタイトルのどちらかを持つウインドウが存在するかどうかを確認しています。(文字列の一部をタイトルに持つウインドウを探索する関数があれば,それを使うのですが,まだ勉強不足です。)
  • AITalkのウインドウを見つける
    hwnd = win32gui.FindWindow(None, ai_title)
    if hwnd == 0:
    hwnd = win32gui.FindWindow(None, ai_title_a)
    win32gui.FindWindow関数はウインドウが存在すれば,ウインドウのハンドル変数hwndを返します。見つからなかった場合はhwndとして0が返されます。ここでは,2つのタイトルについて試しています。
  • AITalkのウインドウが存在しない場合は,起動する
    if hwnd == 0:
    process = subprocess.Popen(ai_path)
    wait_until_state(ai_title, 20, True)
    hwnd = win32gui.FindWindow(None, ai_title)
    subprocess.Popen(ai_path)で,指定されたパスにある実行形式ファイルを起動します。次の,
    wait_until_state(ai_title, 20, True)はai_titleを持つウインドウが現れるまで最大20秒まで待つという自作の関数です。戻り値にウインドウのハンドル変数hwndを返すように作っておくべきでした。
    なお,コード内部でAITalkの実行ファイルのパスを直接指定しています。これでは,他のPCで動作させるときに問題になります。いずれ,環境に合わせて変更できるように,外部の設定ファイルから読み込むように修正する予定です。
    関数wait_until_state()のコードは,以下のようになります。
def wait_until_state(title, max_wait_time, active):
    dt = 0.5
    n = int(max_wait_time/dt)
    time.sleep(0.1)
    for i in range(n):
        a_win = gw.getActiveWindow()
        state = (a_win.title == title)
        if active == False:
            state = not(state)
        if state:
            break
        time.sleep(dt)
    return
  • 引数
    def wait_until_state(title, max_wait_time, active):
     title:ウインドウのタイトル
     max_wait_time:待ち時間の最大値(秒単位)
     active:TureまたはFalseで指定
      True:ウインドウが現れるまで待つ
      False:ウインドウが消えるまで待つ

スクリプトファイルからパラメータリストを読み込む

    # Open and load the slideshow script file
    pptx_path = p_info.pptx

    anim_num = AnimNum()   # Numbers related to animations.
    anim_num.audio = 0 # Index of audio files to be written to ppt/media/.
    anim_num.sld = 1 # Initial value of slide number.  
 
    ani_list = []
    wav_list = []
    nar_list = []
    (ani_list, wav_list, nar_list) = read_script_file(p_info)
  • パスを設定
    pptx_path = p_info.pptx
    pptxファイルのパスは,関数の引数のインスタンスp_infoのメンバー変数pptxで与えられている。
  • アニメーション番号のインスタンスを生成
    anim_num = AnimNum() # Numbers related to animations.
    anim_num.audio = 0 # Index of audio files to be written to ppt/media/.
    anim_num.sld = 1 # Initial value of slide number.
    AnimNumクラスのインスタンス,anim_numを生成し,初期化する。
     anim_num.audio:オーディオファイルaudio1.wav,audio2.wav,・・・の番号
     anim_num.sld::スライドの番号
  • パラメータリストの読み込み
    (ani_list, wav_list, nar_list) = read_script_file(p_info)
     ani_list:anim_numのリスト
     wav_list:合成された音声を書き込むwavファイルのパスのリスト
     nar_list:合成するテキストのリスト
    プレゼンテーションで使うオーディオデータの全てについて必要な情報を処理の順番に従って並べたリストとして取得する。

read_script_file()関数のコードは次のようになっています。

def read_script_file(p_info):
    class LineAtb:
        def __init__(self):
            self.cmd = ''    # Script command of the line.
            self.num1 = '00' # First parameter of the command.
            self.num2 = '00' # Second parameter of the command.

    # Determine the first character for escaping.
    # Lines prefixed with the following strings are not processed 
    # as narration.
    sld_str = '#S'  # for specifying slides
    num_str = '#'   # for specifying click/animation number
    com_str = '%'   # for specifying comment

    script_file_name = p_info.scpt
    pptx_path = p_info.pptx
    with open(script_file_name, encoding='utf-8') as slide_script:
        script_lines = slide_script.readlines()

    name_no_ext = os.path.splitext(os.path.basename(pptx_path))[0]
    # name_no_ext: File name without extension

    # Specify the directory for voice data files
    wav_data_dir = p_info.narr

    anim_num = AnimNum()   # Numbers related to animations.
    anim_num.audio = 0 # Index of audio files to be written to ppt/media/.
    anim_num.sld = 1 # Initial value of slide number. 

    # Read and execute scripts line by line.
    line_atb = LineAtb() # Line attribute.
    ani_list = []
    wav_list = []
    nar_list = []
    for line in script_lines:
        str_len = len(line) 
        if str_len == 0 : # Return to the top of the loop if blank line.
            continue   
        line_atb = check_line(line, com_str, num_str, sld_str, line_atb)    
        if line_atb.cmd == "Comment": # If the command is "Comment",
            continue                  #  return to the top of the loop.
        if line_atb.cmd == "Slide": 
            anim_num.sld = int(line_atb.num1)
            anim_num.clk = int(line_atb.num2)
            anim_num.aut = 0
            continue
        if line_atb.cmd == "Number" :  # The line indicates click number.
            anim_num.clk = int(line_atb.num1) # Click number in the slide.
            anim_num.aut =int (line_atb.num2) # Number of auto animation.
            continue 
        if line_atb.cmd == "Narration" :
            # The line is processed as narration.
            # Determine the file name.
            anim_num.audio += 1
            wav_file_name = create_audio_file_name(name_no_ext, anim_num)
            wav_file_path = wav_data_dir + '/' + wav_file_name
            anim_num_c = copy.copy(anim_num)
            ani_list.append(anim_num_c)
            wav_list.append(wav_file_path)
            nar_list.append(line)
    return ani_list, wav_list, nar_list

AHKで作成した「スクリプトを読んで制御信号を生成する」部分を基にしています。オーディオデータの生成や埋め込みで必要なのは,オーディオファイル,ナレーションのテキスト,そしてどのアニメーションにオーディオを付加するか,という情報なので,これらを出現順に並べたリストを出力します。

<詳しく読む>

実は,今回のAITalk制御用コードの作成で,最も「はまった」のは,このread_script_file()関数の挙動が原因になっています。(頭を使うのに時間がかかった,ということです。もともと暇なときにコーディングをするのですが,このときは,夜寝る前にああでもない,こうでもないと1時間ほど連続して試行錯誤を繰り返しました。

まず第一のつまづきは,クラスのインスタンスのリストを作るところに原因がありました。コードを動かしてみると,できあがったpptxファイルにはプレゼンの一番最後のオーディオファイルしか埋め込まれていないのです。調べると,read_script_file()関数から戻ってきたリストのうち,anim_numに関するリストの内容が,全て一番最後にリストに追加した内容と同じになっていました。この問題は,

            anim_num_c = copy.copy(anim_num)
            ani_list.append(anim_num_c)

のように,copy関数でコピーした値をリストに追加することで解決しました。これについては,時間があれば別稿で説明したいと考えています。

もう1つのつまづきは,スクリプトを解釈する部分が原因でした。スクリプトの解釈では「空白の行があったら,何も処理しないで次の行の処理に移る」というルールに基づいて動作させているつもりでした。そして空白の行であることを「読み込んだラインの文字列数が0なら空白である」ことでlen()関数を使って判断していました。しかし,空白の行といっても改行を含んでいるので長さは0になりません。これでは空白行もナレーションのテキストとして処理され,同じテキストに対する音声合成と保存の操作が複数回繰り返されてしまいました。この場合,処理側で想定していない「同じファイルがあるけど上書きしますか?」みたいなプロンプトが出されてしまいます。(ユーザがEnterキーを押せば次のステップに進み,ファイルもちゃんと作られますが。)

この第2の問題も,新しいread_script()関数では直した(つもり)です。

<閉じる>

パラメータのリストの順に処理していく

ここからが,AITalkのGUIを操作するコードになります。

    list_len = len(ani_list)
    for i in range(list_len):
        anim_num = ani_list[i]
        wav_file_path = wav_list[i] 
        wav_dir = os.path.dirname(wav_file_path)
        wav_file = os.path.basename(wav_file_path)
        nar_text = nar_list[i] 

        pyperclip.copy(nar_text)  # Copy narration text to clipboard.
        forefront_window(hwnd)    # Set AITalk window to the forefront
        pyautogui.hotkey('ctrl', 'a') # Select all text.
        time.sleep(0.2)
        pyautogui.hotkey('ctrl', 'v') # Paste the clipboard contents into the text box.
        pyautogui.hotkey('ctrl', 'alt', 'S') # Synthesis and save command.
        time.sleep(1)        
        hwnd_save = win32gui.FindWindow(None, '名前を付けて保存') # Detect Dialog Box. 
        if i==0:                             # For the first time,
            narr_dir_set(hwnd_save, wav_dir) # set directory name.
        forefront_window(hwnd_save)
        pyautogui.press('tab')       # To ensure the next Alt+n works. 
        pyautogui.hotkey('alt', 'n') # Press Alt+n to set save file name.
        # pyautogui.hotkey('ctrl', 'a')
        # time.sleep(0.5)
        pyperclip.copy(wav_file)     # Copy the file name to the clipboard.
        pyautogui.hotkey('ctrl', 'v')# Paste the clipboard contents into the text box.
        # pyautogui.press('enter')
        time.sleep(0.3)
        pyautogui.hotkey('alt', 's') # To activate the Save Button.
        wait_until_state('情報', 20, True) # Wait until the End of Operation.
        pyautogui.press('enter') # Press Enter to close the window.
    return  
  • リストの長さを取得してループ処理を開始
    list_len = len(ani_list)
    for i in range(list_len):
     リストの長さはani_list,wac_list,scpt_listの3つで同じ。ここではani_listの長さを調べて繰り返し回数list_lenを取得し,カウンタ変数iを使ったループ処理を開始する。
  • パラメータをリストから読み込む
    anim_num = ani_list[i] # アニメーション番号
    wav_file_path = wav_list[i] # 書込むwavファイルのパス
    wav_dir = os.path.dirname(wav_file_path) # パスからディレクトリ名を取得
    wav_file = os.path.basename(wav_file_path) # パスからファイル名を取得
    nar_text = nar_list[i] # 音声合成するナレーション
  • ナレーションのテキストをクリップボードにコピーし,AITalkの編集ウインドウにペースト
    pyperclip.copy(nar_text) # ウインドウズのクリップボードにナレーションをコピー
    forefront_window(hwnd) # 自作の関数でウインドウを前面に移動
    pyautogui.hotkey(‘ctrl’, ‘a’) # Ctrl+aキーでテキスト編集ウインドウ内の全てを選択
    time.sleep(0.2) # 0.2秒待つ(この時間はシステム毎に調整すべきかも)
    pyautogui.hotkey(‘ctrl’, ‘v’) # Ctrl+vキーでテキストウインドウ内にクリップボードをペースト
    time.sleep(1) # 1秒スリープ(この時間もシステム毎に調整すべき)
  • テキストから音声ファイルを保存
    pyautogui.hotkey(‘ctrl’, ‘alt’, ‘S’)
    AITalkのメニュー > ファイル > テキストから音声ファイルを保存…
    で起動される。コードではCtrl+Alt+Sにより起動している。
  • 「名前を付けて保存」ダイアログボックスを検出
    hwnd_save = win32gui.FindWindow(None, ‘名前を付けて保存’)
    ダイアログボックスを検出したら,そのハンドル変数をhwnd_saveに代入している。

「名前を付けて保存」ボックスが検出されたら,ディレクトリ名とファイル名を設定し,音声合成と保存のプロセスを起動する。GUIを使う場合は,次の①~③の操作をする。

「名前を付けて保存」ダイアログボックス

  • 保存ファイルのディレクトリパスを設定(①)
    if i==0:
    narr_dir_set(hwnd_save, wav_dir)
    初回(カウンタ変数iが0)には,ディレクトリをセットする。2回目以降はファイル名のみ設定でよい。
  • ファイル名を設定(②)
    forefront_window(hwnd_save) #「名前を付けて保存」ボックスを前面に(自作関数を使用)
    pyautogui.press(‘tab’) # タブキーを1回押す
    pyautogui.hotkey(‘alt’, ‘n’) # Alt+nキーを押して保存ファイル名のテキストボックスを選択
    pyperclip.copy(wav_file) # 保存ファイル名をクリップボードにコピー
    pyautogui.hotkey(‘ctrl’, ‘v’) # 保存ファイル名テキストボックスにペースト
    time.sleep(0.3)  # 0.3秒スリープ

tabキーを押すのは,経験上,こうすると次のAlt+nが機能するから・・・なので,もう少し考える必要がありそうです。forefront_windowはウインドウを最前面に持ってくるための自作の関数です。

def forefront_window(hwnd):
    # Set AITalk window to the forefront
    ctypes.windll.user32.SetForegroundWindow(hwnd)
    left, top, right, bottom = win32gui.GetWindowRect(hwnd)
    pyautogui.moveTo(left+60, top + 10)
    pyautogui.click()
    return

ウインドウの四隅の座標を取得し,その中にマウスポインタを移動してクリックする,というアナログっぽい操作をしています。ハンドル変数hwndがわかっているので,もっと直接的な方法があれば変更したいです。

  • 保存ボタンを起動し(③),保存が終了まで待ち,OKボタンを押す(④)
    pyautogui.hotkey(‘alt’, ‘s’) # Alt+sを入力
    wait_until_state(‘情報’, 20, True) # “情報”というタイトルのウインドウが現れるまで待つ
    pyautogui.press(‘enter’) # Enterキーを送って,ダイアログボックスを閉じる。

処理時間など

80個のwav形式のファイルを作成するのに数分かかっています。挿入したsleepの時間を調整しても,あまり短くできないと考えています。GUIを介しているので,いたしかたないでしょう。

そろそろ,実行形式のファイルを公開して,使ってみた人からのフィードバックしてもらってコードを手直しすべきなのかもしれません。(需要があるのかどうかは,わかりませんが。)でも,もう少し試してからでないと危ない気がします。

例えば,今回,作ったGUI制御のコードをデバッグしているときのことです。ブレークポイントで停止させVSCodeの編集ウインドウ上で変数の値を確認し,メニューから「継続」を選びます。すると,AITalkのウインドウではなくVSCodeの編集ウインドウをアクティブにしたまま動作が進行します。GUI制御の信号がCtrl+a,Ctrl+vだとすると,VSCodeの編集ウインドウが全部選択され,そこにクリップボードの内容が上書きされてしまいます。

慌てて停止させ,Ctrl+zで編集操作を取り消すことで事なきを得ました。AHKでコードを作成しているときも,デバッグ中に編集中のコードが書き換わってしまい,さっきまで動いていたコードが動かなくなってしばらく原因がわからない,という経験がありました。AHKの場合は,Escキーなどを押すと実行が止まるような設定のコードを追加してからは楽になりました。

また,大事なフォルダを削除してしまう恐れもあります。Pythonコードの中で削除したファイルやフォルダはOSが提供する「ゴミ箱」に移動されるのではなく本当に削除される(と私は理解しています)ので,大変危ないです。

]]>
https://tamlab.fc2.page/category-materials/1676/feed/ 0
PPTファイルに音声を自動で埋め込む その4 GUIを持つアプリ https://tamlab.fc2.page/category-materials/1642/ https://tamlab.fc2.page/category-materials/1642/#respond Thu, 13 Apr 2023 10:17:41 +0000 https://tamlab.fc2.page/?p=1642 pptファイルのスライド切替えとアニメーションにオーディオデータを埋め込むアプリ(といっていいものか・・・)を作りました。初めに作ったAHKでPowerpointのGUIを操作してオーディオデータを設定するソフトは,動作に時間がかかり過ぎました。

そこで,直接pptxファイルにオーディオデータを埋め込む方法に変更しました。試行錯誤の末,何とか講義資料の作成に使えそうなコードができてきました。初めに動作確認のため使っていたものは,動かす度に3回もダイアログボックスを開いてファイルやフォルダを設定するものでした。使いにくいし時間もかかるので,勉強を兼ねてGUIで操作するものを作ることにしました。

アプリの概要

下が作りかけのアプリの操作画面です。必要な最低限の設定しかできないものです。

作成したアプリの操作画面

作成の目的

何回か説明しているので,くどいと怒られそうですが,おさらいします。このアプリの目的は講義用のPowerpointプレゼンテーションのスライド切替えとアニメーションに音声ナレーションを自動で挿入することです。1学期15週の講義のために毎週二,三十枚のスライドがあり,各スライドに数個~30個程度のクリック起動のアニメを使います。他に補足説明や演習用のスライドも必要です。とてもたくさんのオーディオ付加の作業が必要になります。

音声データを挿入するため,スライド切替えやクリックの操作と,操作毎に埋め込むナレーションを「台本」(スクリプトと呼んでいます。)として記述したテキストデータを作ります。このテキストデータから,音声合成ソフトのGUIを操作してオーディオデータのファイルを自動生成するAHKコードを作成しました。(スクリプトを作るのは,音声合成の自動実行のためでもありますが,もう1つは,講義の記録・アーカイブのためです。)

次にとりかかったのが,オーディオデータの生成に使ったのと同じスクリプトファイルを使って,pptxファイルにGUIを介せずオーディオデータを埋め込むPython コードの作成です。ある程度行けそうだ,という感触を得たので,GUIの操作コードを追加しました。

ここまで説明しましたように,元々は個人的な限定された目的のために作ったものです。でも,PPTファイルに音声を自動で埋め込む その2 Pythonによる実装の準備 やPPTファイルに音声を自動で埋め込む その3 Pythonによる実装 で紹介したPythonコードを組み合わせることで,様々な用途に合わせたアプリを作ることができるはずです。

現時点での機能

作りかけで,今後,変更していくことになりますが,以下のようにして使います。

GUIの操作

ⅰ)ファイルやフォルダを指定する
 対象となるファイルや,それらが格納されているフォルダ(ディレクトリ)を指定する。ファイルやフォルダはGUI画面上に①~③で示したテキストウインドウに表示される。
 ①対象となるpptxファイル
 ②音声ファイルを格納したフォルダのパス
 ③プレゼンのスクリプトファイル
  これらのファイルやフォルダは,
 ④ファイル/フォルダのセットボタン
  をクリックすることで,個別に設定できる。クリック1回で設定できるように,
 ⑤自動設定のON/OFF を設定するためのチェックボックスがある。
  ここにチェックマークを付けておくと,pptxファイルを指定するだけで,
  事前に決めたルールに従ったファイルやフォルダが自動で設定される。

ⅱ)スクリプトファイルの内容を確認
 スクリプトファイルが指定されると,その内容がテキストウインドウに表示されれる,
 (⑥スクリプトを表示)今のところ,表示するだけ・・・。編集や保存もできるようにしたい。

ⅲ)オーディオ付加の実行
 ⑦スタートボタン をクリックすることで,オーディオを埋め込む作業が開始する。元のpptxファイルの拡張子の前に”_A”が追加されたpptxファイルが同じフォルダ内に作られる。

自動設定用のファイルの配置とルール

pptxファイル,音声データのフォルダ,スクリプトファイルは,以下のようなルールで作っておくと,pptxファイルを1回セットするだけで,自動で設定される。

ファイルとフォルダの配置(朱書きしてあるのは生成されるフォルダとファイル)

  • 3つのファイルとフォルダはディレクトリの同一階層に配置する
    pptxファイル,音声データのフォルダ,スクリプトファイルは同一のフォルダの同一階層に配置する。
  • ファイルとフォルダの名前(pptxファイルの名前を仮にfilenameとする)
    pptxファイルの名前: filename.pptx,
    音声データのフォルダの名前: filename,
    スクリプトファイルの名前: filename.txt

生成されるファイルとフォルダ

音声データを埋め込んだpptxファイルと,元のpptxファイルを展開したフォルダが生成されます。

 生成されるpptxファイルの名前: filename_A.pptx
 展開されるフォルダ:      filename_zip
  このフォルダは削除しても問題ないが,検討のため現在の設定では残すようにしてある。

今後の予定

 実際の講義で使ったpptxファイルに対して使って,不具合や使いにくいところを修正していく予定です。最低限の機能が備わった後はあまり凝らないようにしますが,以下のような機能の追加を考えています。

  • 自動アニメーションへのオーディオデータの付加
    初めに考えた仕様には含まれているが,Pythonコードでは未実装になっている。必要性はそれほど感していないが検討はする。
  • スクリプト作成補助の機能
    pptxファイルのスライドデータからクリック起動や自動のアニメーションの情報を読み取り,スクリプトの基になるテキストを作る。(講義用pptファイルの処理を省力化するためには,この機能の実装を急いだ方がよいかもしれない。
  • pptのノート機能の利用
    スクリプトをテキストファイルではなく,pptのノートにも記述できるようにする。これができるとスライドの順番の変更が容易になる。
  • Pythonによる音声合成ソフトの制御
    AHKで実行している機能をPythonのPyAutoGUIに移行する。AHKで出来ているので,無理にPythonに移行しなくてもよいのだが,PyAutoGUIを試してみたい気持ちはある。

技術情報の調査

Pythonによる一連のコード作成の過程の中で,webを使って技術情報の検索を何度も繰り返しています。Pythonの使い方や文法に関する検索がメインで,たくさんのサイトのお世話になりました。

肝心のpptxファイルの内部構造についての調査は,不十分なままスタートし不十分なままでここまで来てしまいました。システム開発に関して「自分が働いている階層の少なくとも2階層上と2階層下までの知識が必要」なんて学生に言っていたのに,こんなことではダメですね。

pptxに関する技術情報は,初めにちょこっと調べて見つけた「pptxファイルはzip圧縮されている」という短い投稿しか参考にしていません。研究や技術開発では,こういうシンプルな情報が重要になることが多いようです。

<詳しく>

良く言われていることだと思いますが,研究や技術開発では「〇〇ができた」とか「××というやり方で〇〇ができた」という情報がとても重要になるみたいです。実際,複数の組織が競って研究・開発を進めているけれど未だ実現されていない目標があるとき,「できた」というニュースをきっかけに,どんどん成功例が報告されていく,ということがあります。何かの実現を目標にして,つきつめて考えている人がいるときは,具体的な方法に関する情報が無くても「それができる」ということを知るだけで,前に進めるのかもしれません。

<閉じる>

省力化のツールの自作として目途がついたと言える段階になったので,今一度調べてみました。すると,展開されたpptxファイルの中身を操作して数式を挿入するなど,いろいろ検討している方がいらっしゃることを知りました。やっぱり,ちゃんと調べないとダメだと思いました。

追記  処理時間について

実際に講義に使ったPPTファイルで動作確認すると,80個のオーディオファイルを埋め込むのに要した時間は2.6秒でした。Pythonのtime.time()関数を使って,オーディオデータを埋め込む関数の動作開始から終了までの時間を計測した値です。

一方,AHKを使いPPTアプリをGUIを介して操作する方法では,20分ほどかかっています。圧倒的な差です。(正確な時間を知るためには測り直す必要があるのですが,それをする気にもならないくらい時間がかかります。)

というわけで,今後はPythonを使った処理をメインにしていきます。コードの解説をするのは少しお待ちください。もともとお見せできるような出来のコードではないのですが,作っていく中で変更を繰り返していて,なかなか確定しません。

でも,自分の作ったものについて,ある程度の再現性を保って記録しておくためには,コードを残しておくことは必要です。ある程度安定してから解説記事を投稿していきます。

]]>
https://tamlab.fc2.page/category-materials/1642/feed/ 0
PPTファイルに音声を自動で埋め込む その3 Pythonによる実装 https://tamlab.fc2.page/category-materials/1584/ https://tamlab.fc2.page/category-materials/1584/#respond Tue, 04 Apr 2023 10:43:37 +0000 https://tamlab.fc2.page/?p=1584 Pythonを使って,pptファイルのスライド切替えとアニメーションにオーディオデータを埋め込むことができることを確認しました。これから,具体的なPythonコードについて解説していきます。

これらのコードは,とにかく早期に「動くかどうか確認する」ことを優先して作りました。このため汚いし読みにくいし,後々の利用を考えても使いやすいとは言えません。手直ししながら投稿していく予定です。

<余談を読む>

PPTファイルに音声を自動で埋め込む その2 にも書きましたように,pptxファイルの変更の方法は,公開されている技術文書などから得たものではありません。解凍したpptxファイルの中を観測することで仮説を立て,それに基づいてコードを作って確認したものです。(これは格好つけた言い方で,実際は「こうじゃないかなと思ってやってみたら運よく動いた・・・今のところは。」ですね。)

ですから,考え方が間違っている可能性はありますし,複雑なプレゼンのpptxファイルに対しては期待したようには動かないかもしれません。

なお,pptファイルにナレーションを付加する有料ソフトとして,株式会社エーアイさんが「AITalk® 声プラス」と言う製品を販売しています(私は音声合成に「かんたん!AITalk®3」という製品を使っています)。pptプレゼンテーションに合成音でナレーションを付加するソフトへのニーズはあるのでしょう。

商用ソフトがあるくらいですから,pptファイルにオーディオデータを付加する技術に関する資料は,私が方法を知らないだけで,どこかで入手できることは確かでしょう。さらに,Python-pptxライブラリの整備が進んで,私が作ったコードのほとんどが無用のものになる可能性も大だと思います。

でも,面白いし勉強にはなります。それに大量にある講義用のppt資料にナレーションを付けるためには急いで自動化したいし,何といっても年金生活者には高いソフトは導入できません。やれるところまでやってみようと考えています。

<閉じる>

Pythonコード

それでは,作ったPythonコードについて説明していきます。

pptxファイルの展開/再圧縮用のツール

pptxファイルの中身を調べるために,zip展開(unzip)とpptxファイルに再圧縮するためのコードを作りました。Windowsの操作で,拡張子変更やzip/unzipをしても可能なはずですが,私のPCでは再圧縮して作ったpptxファイルを開くときにエラーになり,修復するとオーディオデータが消えてしまうという不具合がありました。また,多数のファイルのuzip/zipを繰り返すので,Pythonコードを作ることにしました。

作ったコードは以下の2つです。

  • PPTX_Decompress.py
    pptxファイルをunzipします。できあがるのは,pptxファイルのファイル名から拡張子を取り除いたものを名前に持つフォルダになります。
  • PPTX_Compress.py
    指定したフォルダをzip圧縮し,拡張子をzipからpptxに戻します。

PPTX_Decompress.py

リストにコードを示します。

# PPTXファイルを解凍
# PPTX decompress
from tkinter import filedialog
import os
from zipfile import ZipFile

# 対象となるPPTXファイルを指定する
# Specify the target PPTX file.
typ_pptx = [("PPTX File","*.pptx")]
fileName = filedialog.askopenfilename(
    title = "PPTXファイルを指定",
    filetypes = typ_pptx, 
    initialdir = dir) 

# ファイル名から拡張子を取り除いた文字列を取得
# Get the string of the file name without the file extension.
name_no_ext = os.path.splitext(os.path.basename(fileName))[0]

# PPTXファイルをzipファイルとして展開
# Unzip the PPTX file.
with ZipFile(fileName, 'r') as zip_file:
    zip_file.extractall(name_no_ext) 
    zip_file.close()

解説は不要かと思います。一応,書いておきます。ほとんどコメントと同じですが。

  • 必要なモジュールをインポート
    from tkinter import filedialog
    import os
    from zipfile import ZipFile
  • ダイアログボックスを開き,変換するpptxファイルを指定
    typ_pptx = [(“PPTX File”,”*.pptx”)]
    fileName = filedialog.askopenfilename(
    title = “PPTXファイルを指定”,
    filetypes = typ_pptx,
    initialdir = dir)
  • ファイル名から拡張子を取り除いた文字列name_no_extを取得
    name_no_ext = os.path.splitext(os.path.basename(fileName))[0]
     この式文の末尾の[0]を[1]にすると,拡張子の文字列が得られます。
  • PPTXファイルをzipファイルとして展開
    with ZipFile(fileName, ‘r’) as zip_file:
    zip_file.extractall(name_no_ext)
    zip_file.close()
     展開したファイルを格納するフォルダ名を引数にしてextractall関数を呼ぶだけです。元のpptxファイル名の拡張子を除いたものをフォルダ名とします。

PPTX_Compress.py

# PPTX_Compress 展開されたPPTXフォルダをzip圧縮してPPTXファイルを作成
# Create PPTX files by zipping the expanded PPTX folder.
from tkinter import filedialog
import os
import shutil

# 非圧縮状態のフォルダを指定
# Specify a folder in uncompressed state.
pptx_dir = filedialog.askdirectory(
    title = "Specify a folder.",
    initialdir = dir) 
pptx_name = pptx_dir + '_R'

# フォルダを再びzipにアーカイブ
# Archive the folder to zip again.
shutil.make_archive(pptx_name, format='zip', root_dir=pptx_dir)

# Change file extension from .zip to .pptx.
name_src = pptx_name + '.zip'
name_dst = pptx_name + '.pptx'
# If the destination file already exists, 
# delete the file to avoid an error.
is_file = os.path.isfile(name_dst)
if is_file:
    os.remove(name_dst)
os.rename(name_src, name_dst) 

こちらも必要な関数を並べているだけです。少し注意が必要なのは一番最後の,再圧縮したzipファイルの拡張子を.zipから.pptxに書き直すos.rename関数のところです。リネームの結果,同じ名前のファイルが存在するとエラーになります。

そこで,このコードでは元のpptxファイル名の後ろに“_R”を付けて異なるファイル名になるようにしています。さらに同じ名前があった場合は,そのファイルを削除してからリネームするようにしています。

  • 必要なモジュールをインポート
    from tkinter import filedialog
    import os
    import shutil
     shutilモジュールをインポートしている。
  • 圧縮するフォルダを指定し,圧縮後のファイル名を設定
    pptx_dir = filedialog.askdirectory(
    title = “Specify a folder.”,
    initialdir = dir)
    pptx_name = pptx_dir + ‘_R’
     ダイアログボックスでフォルダ名pptx_dirを取得する。さらに,これに“_R”を追加して新しいファイル名(拡張子無し)であるpptx_nameにする。
  • フォルダを再びzipにアーカイブ
    shutil.make_archive(pptx_name, format=’zip’, root_dir=pptx_dir)
     shutilのmake_archive関数で,フォルダpptx_dirをpptx_nameという名前のzipファイルに変換する。
  • リネームの操作
    name_src = pptx_name + ‘.zip’
    name_dst = pptx_name + ‘.pptx’
     zipファイルとリネーム後のpptxファイルの名前を拡張子も含めて決定する。
    is_file = os.path.isfile(name_dst)
    if is_file:
    os.remove(name_dst)
     リネーム後のファイルが存在する場合は,削除。
    os.rename(name_src, name_dst)
     name_srcからname_dstにリネーム。

PPTX_Compress.pyは,作成したPythonアプリケーションでは関数として登録しています。

アプリケーションの仕様

作成しようとしているPythonアプリケーションは,私の講義資料のpptファイルを自動で処理することを目的としています。つまり汎用ではなく限定された条件で使うことになります。処理できるpptファイルや,オーディオファイルの付加の方法も細かい設定はできません。

処理するpptファイルやオーディオ付加の条件は以下のとおりです。

  • pptファイルはpptx形式であること
  • pptファイルのアニメーションは既に設定済みであること
    アプリケーションには新たにアニメーションを設定したりタイミングを招請したりする機能は未実装。
  • pptファイルにオーディオデータは付加されていないこと
    現在の仕様では,オーディオデータが付加されていないことを前提に処理している。既にオーディオファイルが埋め込まれている場合,データの関連づけが崩れて正常に動作しなくなる可能性がある。オーディオ関連のxmlコードやオーディオファイルを削除する機能を加えれば対応は可能だが,現時点では未実装。
  • スライド切替えとクリックで起動されるアニメーションにのみオーディオデータを付加
    クリックではなく直前の動作で起動されるアニメーションへのオーディオ付加は未実装。
  • ユーザが用意するオーディオファイルの命名規則がある
    これは,Pythonコードに関する本質的な問題ではない。現在作っているアプリケーションでは,テキスト形式の「台本」(スクリプトと呼んでいる)に従ってpptxファイルのデータ加工を進めていく。付加するオーディオファイルのファイル名は拡張子の前に”×××_01_02.wav”のようにスライド番号とクリック番号をスクリプトの規則にしたがって並べておく必要がある。スライド番号とクリック番号は2桁0パッドの文字列で表している。(つまり最大値は99に限定される。)

講義資料については,上の条件はほとんど問題になりません。講義中に使う場合は,スライドの画面切り替えやアニメーションを起動するためのマウスの操作と口頭での説明は,教員が受講者の反応を見ながらおこないます。pptファイルに音声を埋め込む必要はありません。ただしデモのための音声や効果音を埋め込むことは皆無ではないので,そのようなファイルでは注意が必要です。

一方,学生さんの予習や復習用に,pptファイルをHTML5形式のスライドショーとしてwebサイトに掲載しています。この場合は,教員がしているクリック操作はかなりたくさんあるのですが,閲覧する人にやってもらうことはそんなに問題ではないでしょう(むしろ,閲覧者が自分でスピードをコントロールできる利点になると思います)。

しかし,閲覧する人にとっては,事前にはクリックの意図が不明なので,とまどうかもしれません。何といっても口頭で説明に関するオーディオや文字の情報が無いので,何を説明しているか困ってしまうかもしれません,講義のpptスライドにオーディオデータを付加しようと考えたのは,この問題に対応したいからです。

アプリケーションで使う関数

アプリケーションで使う関数について説明していきます。このアプリケーションはテキストファイルである.xmlファイルや.xml.relsファイルを扱うので,基本的には文字列処理がメインになります。

私は信号処理や数値シミュレーションなど,数値処理のコードの経験は少しあります。しかし,文字列の処理はファイル名の加工くらいしかやったことがありません。webで調べながら作っているのですが,おかしなコードになっているかもしれません。

コーディングをしている中で自作の関数を付け足していって,主なもので15個くらいあります。オーディオ付加に関連する関数について紹介していきます。

スライド切替え時のオーディオデータを付加する関数

add_slide_trans_audio

スライド切替えの際のナレーションです。1枚のスライドにつき1つのオーディオデータを埋め込むことになります。

def add_slide_trans_audio(f_name, wav_dir, pptx_dir, sld, anim_num):
    # f_name : File name of user defined sound(with extention,".wav").
    # wav_dir : Directory of user defned sound files. 
    # pptx_dir : Directory where pptx files are extracted.
    # sld : Slide information. (Instance variable of SlideInfo class)
    # anim_num : Numbers related to the animations in the slide. (AnimInfo class)

    # The function adds the slide transition audio to the slide.
    #   audio_str : Code for adding audio to the slide xml file.
    #   rels_str  : Code for the slide relation file.(ex. slide1.xml.rels)

    #   In the codes, several parameters are replaced with dummy strings;
    #    name of user defined audio file  => "dummy.wav",
    #    string of relation id (rId)  => "rId",
    #    name of the audio file to be stored in the ppt/media  => "audio.wav".
    
    audio_str = \
        r'<mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.'\
        + r'org/markup-compatibility/2006">'\
        + r'<mc:Choice xmlns:p14="http://schemas.microsoft.com/office/'\
        + r'powerpoint/2010/main" Requires="p14">'\
        + r'<p:transition spd="slow" p14:dur="2000"><p:sndAc><p:stSnd>'\
        + r'<p:snd r:embed="rId" name="dummy.wav"/>'\
        + r'</p:stSnd></p:sndAc></p:transition></mc:Choice><mc:Fallback>'\
        + r'<p:transition spd="slow"><p:sndAc><p:stSnd>'\
        + r'<p:snd r:embed="rId" name="dummy.wav"/></p:stSnd></p:sndAc>'\
        + r'</p:transition></mc:Fallback></mc:AlternateContent>'\

    rels_str =  r'<Relationship Id="rId" Type='\
        + r'"http://schemas.openxmlformats.org/officeDocument/2006/'\
        + r'relationships/audio" Target="../media/audio.wav"/>'

    # Delete slide transition code if it remains.
    sld.xml = delete_transition_code(sld.xml) 

    # Find the max value of rId and get the rId string.
    rid_max = find_max_rid(sld.rels)
    rid_str = 'rId' + str(rid_max +1)

    # Replace the dummy codes in the audio_str to determined strings.
    audio_str = audio_str.replace('dummy.wav', f_name)
    audio_str = audio_str.replace('rId', rid_str)  

    # Determine the name of .wav file to be stored in ./ppt/media/.
    audio_file = 'audio' + str(anim_num.audio) + '.wav'

    # Replace the dummy codes in the rels_str to determined strings.
    rels_str = rels_str.replace('rId', rid_str)
    rels_str = rels_str.replace('audio.wav', audio_file)

    # Insert the xml code to xml code of the slide.
    sld.xml, err = insert_xml(sld.xml, '</p:clrMapOvr>', audio_str, 0, 1)

    # Insert the rels_str to xml.rels code of the slide.
    sld.rels, err = insert_xml(sld.rels, 'relationships">', rels_str, 0, 1)

    #  Write the slide information code to the silde.xml and slide.xml.rels.
    write_slide_info(sld, pptx_dir)

    # Copy the user defined audio file to ./ppt/media/
    audio_dir = pptx_dir + '/ppt/media'
    if not os.path.isdir(audio_dir):
        os.mkdir(audio_dir)
    audio_path = pptx_dir + '/ppt/media/' + audio_file
    wav_file_path = wav_dir + '/' + f_name
    shutil.copyfile(wav_file_path, audio_path)
    return
引数
def add_slide_trans_audio(f_name, wav_dir, pptx_dir, sld, anim_num):
  • f_name:ユーザが用意したwav形式のオーディオファイルの名称
    スライド番号,クリック番号,自動アニメーションの番号を文字列として含む。
  • wav_dir:オーディオファイルを格納しているディレクトリのパス
  • pptx_dir:zip展開したpptxファイルのディレクトリ名
    pptxファイルの名前から拡張子を除いた文字列になる。
  • sld:処理するスライドの情報をまとめたクラスSlideInfoのインスタンス
       sld.ind:スライド番号n
       sld.xml:ppt/slides/に格納されているsliden.xml内の文字列
       sld.rels:ppt/slides/_rels/に格納されているsliden.xml.rels内の文字列
  • anim_num :アニメーション番号をまとめたクラスAnimNumのインスタンス
       anim_num.sld:スライドの番号n(sld.indとかぶっています!)
       anim_num.clk:クリックの番号
       anim_num.aut:自動アニメーションの番号
       anim_num.audio:ppt/media/の中に格納するオーディオファイルの番号

クラスを使っていますが,構造体として使っているだけです。あと,Pythonではクラスの引数は参照渡しとなるというので,処理結果を呼び出し側に戻すことも考えてクラスインスタンスを採用しています。

ただし,インスタンスのメンバ変数(と呼ぶのかな?)が重複していたり使い方にはいろいろ問題があるみたいなので,後で変更していくことになるでしょう。

なお,これらのクラスは呼び出し側で次のように定義しています。

class PathInfo:
    def __init__(self):
        self.pptx = ''   # pptx file path.
        self.narr = ''   # Narration folder path.
        self.scpt = ''   # Script file path.
        self.idir = ''   # Initial directory.


class SlideInfo:
    def __init__(self):
        self.ind = 0     # Index of the current slide.
        self.xml = ''   # Content of .xml file.
        self.rels = ''  # Content of .xml.rels file.


class AnimNum:
    def __init__(self):
        self.sld = 0    # Slide number.
        self.clk = 0    # Click number.
        self.aut = 0    # Auto animation number.
        self.audio = 0  # Audio file index in ppt/media/
add_slide_trans_audioがやっていること

PPTファイルに音声を自動で埋め込む その2 Pythonによる実装の準備 で解説したスライド切替えオーディオデータの付加をするための関数です。zip展開したpptxディレクトリの下にある,./ppt/slides/sliden.xmlと./ppt/slides/_rels/sliden.xml.relsの内容を書き換え,./ppt/mediaの下にオーディオファイルをaudiom.wavという名前にして配置します。mはプレゼンテーション内で重ならないような通し番号になります。手順は次のようになります。

ⅰ)rIdの最大値を見つけ,その値に1を加えて新しいrIdを決める

ⅱ)sliden.xmlとliden.xml.relsに挿入する文字列を作る

ⅲ)sliden.xmlとliden.xml.relsに文字列を挿入する

ⅳ)ppt/media/の下に,オーディオファイルをコピーする

それでは,各手順をもう少し詳しくみていきます。

ⅰ)rIdの最大値を見つけ,その値に1を加えて新しいrIdを決める
 sliden.xmlまたはsliden.xml.relsの中を検索して”rIdk“(kは10進数の数字)となっている部分を見つけ,kの最大値を求める。(sliden.xmlとsliden.xml.relsのrIdは共通のはずなので,どちらを使ってもよい。)この値に1を加えて新しいrIdの文字列を決定する。

以下に,オーディオを付加していないシンプルなsliden.xml.relsの例を示します。

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

この例では”rId1″となっている文字列が1つだけ見つかります。”rIdk“のkの最大値は1です。新しいrIdの値は既に使われているものと重ならないように選べばよいので,最大値1に1を加えて得られる”rId2″を新規のrIdとして使うことにします。

<もう少し詳しく読む>

編集を繰り返したpptファイルの場合,rIdの文字列は,”rId1″,”rId3″,”rId4″,・・・のように,1ずつ増加しているのではなく,途中が抜けている可能性があります(確認したわけでがありません)。しかし,その場合でも,最大値を見つけてそれに1を加えることで既存のrId文字列と重ならないものが作れます。それで,最大値に1を加えるというシンプルな方法を採用しました。

<閉じる>

rIdの最大値を見つける部分は,以下のコードになります。

    # Find the max value of rId and get the rId string.
    rid_max = find_max_rid(sld.rels)
    rid_str = 'rId' + str(rid_max +1)

find_max_ridは,自作の関数で,引数に文字列を入れて処理すると,その文字列の中のrId文字列の表している最大の値を整数で返します。

次のrid_str =・・・で,その値に1を加えて新しいrId文字列を作っています。

関数find_max_ridは次のようになっています。

def find_max_rid(str):
    # Find the max of rId number
    rid_num = 0
    rid_max = 0
    pt = 0
    while pt >= 0  :
        (pt, rid_num) = find_rid(str,pt)
        if rid_num >= rid_max :
            rid_max = rid_num
    return rid_max
find_max_rid(str)
  • str:文字列
    この中に含まれているrIdの最大値を求め,整数値で返す。

find_max関数の中では,やはり自作のfind_rid関数を呼んでいます。find_rid(str, pt)は,文字列strの先頭からpt番目の文字から探索を始め,最初に見つかったrId文字列の数値を読み取ります。関数の戻り値は,見つけたrId文字列の直後の位置pt_stopと,読み取った数値rid_numです。rId文字列が存在しない場合は,pt_stopの値は-1にして返します。

これを利用して,find_max_ridではrId文字列の数値を読み取っていき,ptの値が負になるまで繰り返しています。

def find_rid(str,pt):
    pt_start = find_slice_position('"rId',1, pt, str) 
    pt_stop  = find_slice_position('"',-1,pt_start,str)
    if pt_start == -1 or pt_stop == -1 :
        return (-1,0)
    else :
        rid_num = int(str[pt_start:pt_stop])
        return (pt_stop,rid_num)
find_rid(str, pt)
  • str:rId文字列を含んでいる文字列
  • pt:探索を開始する位置(文字列の先頭を0とする)

さらに,find_ridの中でfind_slice_positionを呼んでいます。

def find_slice_position(target_str,d, pt, str) :
    # Finds the target-string and returns sice position.
    # d = 1: after the target,d =-1 : befor the target
    tag_str_len = len(target_str)
    pt_temp = str.find(target_str,pt)
    if pt_temp ==-1 :
        return -1
    else :
        if d == 1 :
          slice_pt = pt_temp + tag_str_len
        else :
          slice_pt= pt_temp
        return slice_pt
find_slice_position(target_str,d, pt, str) 
  • target_str:目標とする文字列
  • d:d>0の場合目標とする文字列の直後,d<0の場合は直前の位置を返す
  • 探索を開始する位置(文字列の先頭を0とする)
  • str:探索の対象とする文字列
  • 戻り値は位置を示す整数値。target_strが見つからないときは,-1を返す。

ⅱ)sliden.xmlとliden.xml.relsに挿入する文字列を作る
 これらの文字列は,関数のコードの中に埋め込まれています。連続した文字列なので,見やすくなるようにところどころにバックスラッシュを入れてあります。

    audio_str = \
        r'<mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.'\
        + r'org/markup-compatibility/2006">'\
        + r'<mc:Choice xmlns:p14="http://schemas.microsoft.com/office/'\
        + r'powerpoint/2010/main" Requires="p14">'\
        + r'<p:transition spd="slow" p14:dur="2000"><p:sndAc><p:stSnd>'\
        + r'<p:snd r:embed="rId" name="dummy.wav"/>'\
        + r'</p:stSnd></p:sndAc></p:transition></mc:Choice><mc:Fallback>'\
        + r'<p:transition spd="slow"><p:sndAc><p:stSnd>'\
        + r'<p:snd r:embed="rId" name="dummy.wav"/></p:stSnd></p:sndAc>'\
        + r'</p:transition></mc:Fallback></mc:AlternateContent>'\

    rels_str =  r'<Relationship Id="rId" Type='\
        + r'"http://schemas.openxmlformats.org/officeDocument/2006/'\
        + r'relationships/audio" Target="../media/audio.wav"/>'
  • audio_str
    スライド切替えのオーディオを付加するためのsliden.xmlに追加する文字列。Pythonコードの中では,書き換える部分(赤い文字で表記)を,次のように仮の文字列にしてある。
      rId文字列:数字部分を抜いた”rId”,
      ユーザが用意したオーディオファイルの名前:”dummy.wav”
  • rels_str
    xmlコードで指示したオーディオデータが実際に埋め込まれたppt/media/下にあるwav形式ファイルのどれに対応するかの関係を表す文字列。書き換えのための仮の文字列は,
      rId文字列:”rId”,audio_strと同じに揃える
      ppt/media/下に書き込むwav形式ファイルの名前:”audio.wav”

文字列の置き換えは,標準の文字列関数で実行しています。

    # Replace the dummy codes in the audio_str to determined strings.
    audio_str = audio_str.replace('dummy.wav', f_name)
    audio_str = audio_str.replace('rId', rid_str)  

文字列audio_strの中のダミーの文字列”dummy.wav”を引数で与えたファイル名のf_nameで置き換えます。次に,”rId”もrIdの最大値+1で計算した値を使ったrId文字列で置き換えます。

以上で,sliden.xmlとsliden.xml.relsに挿入する文字列の準備ができました。次にこの文字列を挿入します。

    # Insert the xml code to xml code of the slide.
    sld.xml = insert_xml(sld.xml, '&lt;/p:clrMapOvr>', audio_str, 0, 1)

    # Insert the rels_str to xml.rels code of the slide.
    sld.rels = insert_xml(sld.rels, 'relationships">', rels_str, 0, 1)

ここで,自作の関数insert_xmlを使っています。

def insert_xml(input_str, target_str, insert_str, start_pt, d):
    # If input_str already exists, do nothing and return.
    slice_pt = find_slice_position(insert_str,1, start_pt, input_str)
    if slice_pt >=0 : 
        return input_str, -1
    # Find the slice position before (d&lt;0) or after (d>0) the tareget_str.
    else : 
        slice_pt = find_slice_position(target_str,d, start_pt, input_str)
    if slice_pt == -1 :
        return input_str, -1
    else :
        input_str_back = input_str[slice_pt : ]
        input_str = input_str[0 : slice_pt] + insert_str + input_str_back
        return input_str, 1

文字列のスライスを使った処理になります。中で自作のfind_slice_positionを使っています。これは文字列オブジェクトのstr.find関数でも問題なくできます。無理に関数にしなくてもよかったのかもしれませんが,やっていることが理解しやすくはなります。

insert_xml(input_str,  target_str,  insert_str,  start_pt,  d)
  • input_str:挿入する対象となる文字列
  • target_str:目印(目標)にする文字列
  • insert_str:
    文字列target_strを見つけたら,その直後または直前にinsert_strを挿入する。
  • d: d>0なら直後,d<0なら直前に挿入
    d<0の場合の処理は,str.find関数だけでよいのですが,同じ関数の形で使えるようにしておきました。でも,target_strの直前に挿入する用途はまだありません。

挿入したら,文字列をファイルsliden.xmlとsliden.xml.relsに書き込みます。

    #  Write the slide information code to the silde.xml and slide.xml.rels.
    write_slide_info(sld, pptx_dir)

2つのファイルに書き込むための関数を作りました。

def write_slide_info(slide, pptx_dir):
    typ = type(slide)
    i_sld = slide.ind
    slide_file = pptx_dir + '/ppt/slides/slide'+ str(i_sld) + '.xml'
    rels_file = pptx_dir + '/ppt/slides/_rels/slide'+ str(i_sld) + '.xml.rels'
    
    write_file(slide_file, slide.xml)
    write_file(rels_file, slide.rels)
write_slide_info(slide, pptx_dir)
  • slide:クラスSlideInfoのインスタンス
  • pptx_dir:pptxファイルをzip展開したトップのディレクトリ

この関数の中では自作のwrite_fileを使っています。中身の説明は不要だと思います。

def write_file(f_name,input):
    f = open(f_name, 'w', encoding = 'UTF-8')
    f.write(input)
    f.close()
    return 1

ⅳ)ppt/media/の下にオーディオファイルをコピーする

    # Copy the user defined audio file to ./ppt/media/
    audio_dir = pptx_dir + '/ppt/media'
    if not os.path.isdir(audio_dir):
        os.mkdir(audio_dir)
    audio_path = pptx_dir + '/ppt/media/' + audio_file
    wav_file_path = wav_dir + '/' + f_name
    shutil.copyfile(wav_file_path, audio_path)
    return

shutil(シューティル,Shell Utilitiesの略だそうです)モジュールの関数,shutil.copyfileを使っています。

クリックで起動されるアニメーションにオーディオを付加する関数

add_click_actv_audioは処理の内容は,スライド切替え用の関数add_slide_trans_audioと,ほとんど同じです。異なっているのは,コードを挿入する位置が複数(1つ以上)ある,sliden.xmlに書き込む文字列が異なる,クリックのIdを読み取って書き込む必要がある,などの点です。

また,この関数add_click_actv_audioでは,指定された順番を持つクリック記述の位置1か所だけにコードを埋め込むようにしてあります。

def add_click_actv_audio(f_name, wav_dir, pptx_dir, sld, anim_num):
    # f_name : File name of user defined sound(with extention,".wav").
    # wav_dir : Directory of user defned sound files. 
    # pptx_dir : Directory where pptx files are extracted.
    # sld : Slide information. (Instance variable of SlideInfo class)
    # anim_num : Numbers related to the animations in the slide. (AnimInfo class)

    # The function adds the click-activated audio to the slide.
    #    audio_str : xml strings describing audio data 
    #    rels_str  : Code for the slide relation file.(ex. slide1.xml.rels)    
    #    name of user defined audio file  => "dummy.wav",
    #    string of relation id (rId)  => "rId",
    #    name of the audio file to be stored in the ppt/media  => "audio.wav".
    audio_str = \
        r'&lt;p:subTnLst>&lt;p:audio>&lt;p:cMediaNode>'\
        + r'&lt;p:cTn display="0" masterRel="sameClick">'\
        + r'&lt;p:stCondLst>&lt;p:cond evt="begin" delay="0">'\
        + r'&lt;p:tn val="dummyNum"/>&lt;/p:cond>&lt;/p:stCondLst>&lt;p:endCondLst>'\
        + r'&lt;p:cond evt="onStopAudio" delay="0">&lt;p:tgtEl>'\
        + r'&lt;p:sldTgt/>&lt;/p:tgtEl>&lt;/p:cond>&lt;/p:endCondLst>&lt;/p:cTn>'\
        + r'&lt;p:tgtEl>&lt;p:sndTgt r:embed="rId" name="dummy.wav"/>'\
        + r'&lt;/p:tgtEl>&lt;/p:cMediaNode>&lt;/p:audio>&lt;/p:subTnLst>'
    rels_str =  r'&lt;Relationship Id="rId" Type='\
        + r'"http://schemas.openxmlformats.org/officeDocument/2006/'\
        + r'relationships/audio" Target="../media/audio.wav"/>'
    
    # Find the max value of rId
    rid_max = find_max_rid(sld.rels)
    rid_str = 'rId' + str(rid_max +1)
    
    (clk_id, pt) = find_nth_click(sld.xml, anim_num.clk)

    audio_str = audio_str.replace('dummy.wav', f_name)
    audio_str = audio_str.replace('rId', rid_str)  
    audio_str = audio_str.replace('dummyNum', clk_id) 

    audio_file = 'audio' + str(anim_num.audio) + '.wav'

    rels_str = rels_str.replace('rId', rid_str)
    rels_str = rels_str.replace('audio.wav', audio_file)

    sld.xml, err = insert_xml(sld.xml, '&lt;/p:childTnLst>', audio_str, pt, 1)
    sld.rels,err = insert_xml(sld.rels, 'relationships">', rels_str, 0, 1)
    write_slide_info(sld, pptx_dir)

    audio_dir = pptx_dir + '/ppt/media'
    if not os.path.isdir(audio_dir):
        os.mkdir(audio_dir)
    audio_path = pptx_dir + '/ppt/media/' + audio_file
    wav_file_path = wav_dir + '/' + f_name
    shutil.copyfile(wav_file_path, audio_path)
    return

関数の引数,処理の流れ,使っている関数などはadd_slide_trans_audioとほぼ同じなので,異なるところだけ説明します。

挿入するコード

sliden.xmlに挿入する文字列と挿入する位置が異なります。文字列は,以下の代入文で定義しています。

 audio_str = \
        r'<p:subTnLst><p:audio><p:cMediaNode>'\
        + r'<p:cTn display="0" masterRel="sameClick">'\
        + r'<p:stCondLst><p:cond evt="begin" delay="0">'\
        + r'<p:tn val="dummyNum"/></p:cond></p:stCondLst><p:endCondLst>'\
        + r'<p:cond evt="onStopAudio" delay="0"><p:tgtEl>'\
        + r'<p:sldTgt/></p:tgtEl></p:cond></p:endCondLst></p:cTn>'\
        + r'<p:tgtEl><p:sndTgt r:embed="rId" name="dummy.wav"/>'\
        + r'</p:tgtEl></p:cMediaNode></p:audio></p:subTnLst>'

見づらいので,整形したものを示します。赤い文字で強調している部分が,ダミーの文字列で,sliden.xmlとsliden.xml.relsから読み取って作った文字列と置き換えてから,ファイルに書き込みます。

        <p:subTnLst>
           <p:audio>
             <p:cMediaNode>
               <p:cTn display="0" masterRel="sameClick">
                 <p:stCondLst>
                   <p:cond evt="begin" delay="0">
                     <p:tn val="dummyNum" />
                   </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="rId" name="dummy.wav" />
               </p:tgtEl>
             </p:cMediaNode>
           </p:audio>
         </p:subTnLst>

rId文字列とユーザが用意したwav形式ファイルの名前dummy.wavに関しては,スライド切替えオーディオ用の関数add_slide_trans_audioと同じ処理になります。文字列audio_strの中にあるdummyNumは,クリックに関係するIdで,これは対応するクリックに関する記述から読み取る必要があります。

オーディオを付加していないときのクリックに関する記述の例は次のようになっています。

   <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:cTn>
     </p:par>
   </p:childTnLst>

“clickEffect”という文字列を見つけ,遡って探索して最初に見つけた”id=”の次にクリックのIdがあります。この例では”5″となっています。

そして,”clickEffect”の後ろに探索していって,初めに見つけた</p:childTnLst>の直後にダミーの文字列を書き換えたaudio_strを挿入します。

異なっているのは,以下の文があることです。

    (clk_id, pt) = find_nth_click(sld.xml, anim_num.audio)

ここで使っている関数find_nth_click(n番目のクリックを見つける関数)が,指定された番号のクリックの記述を見つけ,クリックのId番号を取得して返す関数です。

def find_nth_click(sld_str, n_clk):
    # Find the n-th click-effect description in the slide.xml string.
    # Return the click ID number and the 'clicEffect">' position.
    target_str = 'clickEffect">'
    pt = 0
    i_clk = 0
    strlen = len(target_str)
    while i_clk &lt;= n_clk-1 :
        pt = sld_str.find(target_str, pt) 
        if pt >=0:
            i_clk += 1
            pt = pt + strlen
        else:
            break
    pt_start = find_slice_position('id="',1, pt-120, sld_str) 
    pt_stop = find_slice_position('"',-1, pt_start, sld_str)
    id = sld_str[pt_start : pt_stop]
    return id, pt
find_nth_click(sld_str, n_clk)
  • sld_str:sliden.xmlの内容の文字列
  • n_clk:クリックの番号
  • 関数の戻り値は,クリックのIdを示す文字列と,n番目のクリック記述の中のclicEffect”>という文字列の直後のスライス位置です。

この関数の中身の説明は不要かと思いますが,念のため書き残しておきます。

  • n_clk番目のtarget_str = “<“clickEffect”>を見つける
    pt = 0 
    i_clk = 0
    strlen = len(target_str)
    while i_clk <= n_clk-1 :  
    pt = sld_str.find(target_str, pt)
    if pt >=0:
    i_clk += 1
    pt = pt + strlen
    else:
    break
     whileループを抜けると,ptはn_clk番目の<“clickEffect”>の直後のスライス位置になっています。
  • 遡って探索して初めのId=”を見つける部分
    pt_start = find_slice_position(‘id=”‘,1, pt-120, sld_str)
     n_clk番目の<“clickEffect”>の120文字前から探索を開始し,見つけたid=”の直後をpt_startとする。
    pt_stop = find_slice_position(‘”‘,-1, pt_start, sld_str)
     pt_startの後ろの”を見つけ,その直前をpt_stopとする。
    id = sld_str[pt_start : pt_stop]
     これで数値の文字列が取得できる。

説明が長く,わかりにくくなってきました。

商売柄,「読んだ人が同じ程度のシステムを再構築できる」ような解説を工夫しなければならないのでです。こういうサイトをのぞいてみようとする方であれば,「ヤじるし」よりはコーディングスキルがあるでしょうから,それに頼って雑な書き方をしてしまいました。暇になったら,書き直していくつもりです。

今回の投稿で解説した関数を使って,PPTファイルのスライド切替えとアニメーションにオーディオデータを追加するコードを作成しました。少し複雑でスライド枚数の多いプレゼンテーションに使って,コードを改良していく予定です。

その4 GUIを持つアプリ に続く。

]]>
https://tamlab.fc2.page/category-materials/1584/feed/ 0
PPTファイルに音声を自動で埋め込む その2 Pythonによる実装の準備 https://tamlab.fc2.page/category-materials/1562/ https://tamlab.fc2.page/category-materials/1562/#respond Sat, 01 Apr 2023 23:37:41 +0000 https://tamlab.fc2.page/?p=1562 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による実装 に続く

]]>
https://tamlab.fc2.page/category-materials/1562/feed/ 0