教材 | TamLab 授業のおもちゃ箱 https://tamlab.fc2.page 情報系科目の講義資料とツールを公開 Mon, 29 Jul 2024 12:18:12 +0000 ja hourly 1 https://wordpress.org/?v=6.7.2 PowerpointとiSpring Suiteでweb漫画を作れるか? https://tamlab.fc2.page/category-books/category-books-art/2329/ https://tamlab.fc2.page/category-books/category-books-art/2329/#respond Tue, 23 Jul 2024 00:25:50 +0000 https://tamlab.fc2.page/?p=2329 突然なのですが、PowerpointとアドインソフトiSring Suiteを使ってweb漫画を作れるか? について試してみました。

iSpring SuiteはPowerpointでeラーニング教材を作るためのソフトウェア製品です(iSpring社のサイト )。私はこれを主に工学部の学生さん向けの教材作りに使ってきました。

どうして教材作りのためのソフトウェアでweb漫画を描こうなんて思ったのか。その説明をしなければなりません。でもその前に、せっかちな方のために結論から申し上げると、

  • PowerpointとiSpring Suiteの現在の仕様では不十分
    元々スライドショーによるプレゼンを目的とした製品なので、web漫画用には向いていないところがある。また、iSpring Suiteには正確に変換できないPowerpointアプリの効果があることもわかった。
  • 漫画・イラスト・絵本などの新しいメディアとしての可能性
    絵(グラフィックス)と文章、セリフ(“吹き出し”)などを組み合わせた作品の閲覧や配布の方式としては可能性がありそう。

結果を早く見たいかたは、以下のリンクをクリックしてください。別タブで表示されます。画面内でのマウスクリックまたは下向き矢印でコマ送りされます。詳しいことは、投稿の後ろの方で説明します。

<サンプル(通常版)>   

<サンプル(オーバーレイ版)>

そもそも教材と漫画では設計思想がだいぶ異なります。実際、私が作ってきた教材は漫画のようなアナログで芸術性も求められるものとは対極のものです。グラフィックスもデジタルで作っているし、内容は技術系のものばかりです。

ですから、「どうして唐突に漫画の話になったのか」が気になると思います。

発端

友人の佐川俊彦に手紙を出したのは今年(2024年)の6月の末のことです。佐川とは小中高と同窓でした(佐川の御父君の転勤のため空白期間はありますが)。

<注を読む>

「読書日記」のどこかの投稿にも書いたと思いますが、このwebサイトの投稿では、ちゃんとした書籍の著者や研究者・学者の氏名には敬称をつけません。それが敬意を表すことになるからです。

研究者、学者、クリエイター(気取った呼び方ですが)でも何でも、その個人を独立したプロとして認めている場合は敬称をつけないのです。少なくとも理科系の学問ではそうなっています。“アインシュタインの相対性理論”であって、“アインシュタイン先生の相対性理論”などとは呼びませんよね。〇〇教授の研究室に院生□□君がいたとします。公の場では、〇〇教授は「□□の提案した方法」、□□君は「〇〇らの提案によれば・・・」のように発言します。文科系の学問ではどうなのかはわかりませんが。

もっとも、佐川とは会えばタメグチで話す仲です(めったに会ったり話したりしないのですが)。敬称を略しても、互いに何てことはないのです。

<注を閉じる>

佐川は、長らく漫画編集者として働いていて、Juneという雑誌を立ち上げたことで知られています。現在は京都精華大学マンガ学部の教授です。(先日、図書館に行ったら、週刊文春のコラムにインタビュー記事が掲載されていた。7月4日号?。)

その彼に手紙を出さなければならない用事があり、その後、電話とメールでのやりとりが続きました。

そのさい、幾つか質問をしたのです。

  • イラストを引用するとき気を付けるべき著作権の問題
    在籍中は教育目的の利用だったので著作権の問題はそれほど気にしていなかった。このwebサイトではイラストを引用することがあるが、「記憶スケッチ」をしたり模写も一部のみに留めたりするなど、気を使っている。
  • web漫画の「縦スクロール」ってどうなの?
    ここで「縦スクロール」とはスワイプによりページ単位ではなく1コマ単位で縦に閲覧していくウェブトゥーン(Webtoon)のこと。スマホの縦長の画面に適しているらしいのでスライドショー教材に利用できるかしらと興味を持った。
  • 漫画業界でのAIの利用について
    昨今は、どうしても、この話題が出てくる。

やはり何でもその道のプロに聞いてみるもので、いろいろ面白いことがわかりました。「著作権の問題」、「AIと漫画」については別の機会にゆずることにします。二つめの質問「web漫画の切り替え方式」に関してお話していきます。

本題に入る前に、「導入」として2コマ漫画の例を示しましょう。

「世界で一番短い推理漫画」(ヤじるし) 書いた当時は鉛筆描き 筒井康隆並びに青山豪昌に負うところ多し

これは、今をさかのぼること二十数年前に私が描いた2コマ漫画を、記憶を基に再現したものです。当時小学生だった子供が、宿題かなにかの課題で提出しなければならない、でも描けない何とかしてくれ、と泣きついてきたのです。提出の日の朝に。

それで、出勤を15分ほど遅らせて描きました。「これは原案だからね。後は自分で何とかしなさい。」と言って鉛筆で描きました。コマ数も4つなんて描けないので2コマにしました。その後、色くらいは本人が塗ったのかもしれませんが、ほぼそのまま提出したようです。

<詳しく読む>

1コマ目の人物は、1994年連載開始の推理漫画の主人公だということはわかるでしょう。ストーリーというかプロット(2コマ漫画にそんなものあるのか?)の方は、どうでしょうか? セリフは、筒井康隆のショートショートにあった「世界一短い推理小説」(題名は間違っているかも)を借りてきました。筒井康隆の意図を活かすのなら、2コマ目だけを使った1コマ漫画にすべきなのです。でも課題が「複数コマの漫画を描く」だったので1コマ目を加えたのだと思います。

というわけで「キャラクターもセリフも他人の作品のパクりじゃないか」と言われれば、その通りです。気持ちは「本歌ほんか取り」なんですけどね。

<閉じる>

この例でもわかるように、通常の漫画は複数の「コマ」で構成されており、それが一度に画像として表示されています。新聞の4コマ漫画では4コマ全部が提示され、複数のページにまたがることはありません。雑誌の場合は、1つのページが複数のコマに分割されていて、それが複数のページにわたって提示されます。

漫画のリテラシー

上に述べたように一度に目に入ってくる複数のコマを、読者は1つ1つ順番に読んでいきます。新聞などの4コマ漫画の場合は、上から下へという順になります。

4コマ漫画のコマの並びは1次元です。ほとんどの人は上から順番に読んでいくと思います。(もちろん生まれて初めて4コマ漫画を読む人、赤ちゃんとか、がどんな順番で読むかは、調べてみないとわかりませんが。)

一方、漫画雑誌の場合は2次元つまり縦と横に拡がった配置になります。そして、日本の漫画雑誌の場合は、右から左、上から下という順にコマを読んでいきます。

<詳しく読む>

実際の読み手の眼や頭の動きは、もっと複雑なことをしているのかもしれません(既に誰かが調べているかもしれません)。読み手はあるコマを見ていると同時に、前後の(あるいはページ全体の)コマに描かれている情報も利用している可能性があります。また、コマの間の「つながり」を周辺視覚から得ているかもしれません。複雑になってしまうので、この辺りのことには深くつっこまないことにします。

<閉じる>

実はこのような読み方が公式(?)なのだ、ということを今回調べて初めて知りました、そんなことを教えてもらったことはありませんし、親や友人・先輩が読み方を教えてくれたという記憶もないです。でも当たり前のように漫画は読めています。たぶん日本にいるほとんどの人は意識しないで漫画を読んでいるでしょう(例外はあるみたいです)。しかし、このような漫画の「読み方のリテラシー」は万国共通ではないようです。

以下は佐川から電話で聞いた話の、web漫画に関する部分の要点です。

  • 漫画が読めない人がいる
    京都精華大学の彼の研究室には中国や韓国からの留学生が在籍しています。その中には、雑誌の漫画が読めない人がいて、例えば大きいコマから先に読んでしまう、というのです。欧米の雑誌は左から右の順なので、日本の漫画を海外で出版するときは版を左右反転するということは知っていました。しかし、コマを読み取れない人がいるということは意外でした。
  • 絵本
    そのせいかどうかわからないけれど、現在、中国や韓国で絵本に興味を持つ若い人が増えているそうです。ほとんどの絵本は1ページまたは見開き2ページで1枚の絵になっているので、コマの読みとりの問題はなくなります。
  • 縦スクロールのweb漫画
    佐川によると「縦スクロールのweb漫画の出現の背景には、簡単で安易な方に流れる傾向がある」ということです。縦スクロール方式では、スマートフォンの画面に一コマの画像が表示されていて、縦方向のスワイプで上下にスクロールします。これなら、コマの配置は一次元つまり一方向になり、二次元配置の場合の読者の読みとりリテラシーは不要になります。要は「読むのが楽」ということになります。
    今後、漫画の新しい閲覧や配信の技術が出てくるとして、それは読者の「認知的コスト」や「認知的負荷」を小さくする方向、つまり楽な方向に進んでいく。その結果、印刷された漫画の単行本や雑誌は読まれなくなっていくだろう・・・というのが佐川の意見です。

漫画のコマの読みとりリテラシーがあること、リテラシーの無い人でも読めるように工夫されているのがウェブトゥーン(Webtoon)に代表される縦スクロールのスマホ漫画であり、漫画業界もその影響を受けていくだろう、ということです。

縦スクロールの漫画を閲覧してみたら

Androidスマホを使って、縦スクロールの漫画(ウェブトゥーン)を閲覧してみました。ほんの1,2の例だけです。私が気が付いたことをまとめると以下のようになります。

  • コマとコマの間に余裕を持たせている
    たぶん、1つのコマを閲覧しているときに上下のコマが画面に入らないようにするためだろう。
  • 上下のコマの情報もある程度把握できる
    とは言え、コマとコマの間隔はあまり広くはなっていないので、上下のコマの情報が把握できる。読み終わった上のコマや、次にくる下のコマを目に入れた状態で画面の切り替えが可能になる。
  • 背景
    セリフの“吹き出し”がコマからはみ出している作品があった。また、別の作品ではコマとコマの間の背景にあたる部分に紙の表面のような模様(テキスチャ)がつけられていた。個人的には、これらの演出により、コマとコマのつながりやスクロールの“流れの速さ”を実感できるように感じた。また、縦方向のスクロールはスワイプの動作に比例したアナログな動きになっている。

以上のように、コマからコマへの移動や注目は楽にできると同時に、漫画全体の流れも意識できるように工夫されているようです。これは、元々複数のコマを同時に見せていた紙の漫画を、アナログ感を残してデジタル化したことから来るのだと思います。1枚1枚の静止画を切替えながら見せるスライドショーをルーツに持つPowerpointとの違いでしょう。

他にもいろいろと工夫がされているところがあるだろうと思います。私は上に紹介したような演出が印象に残りました。「部分に注目させると同時に全体の流れも意識させる」ための工夫はPowerpointを使ったプレゼンでも重要になるからです。

PowerpointとiSpringでweb漫画を作ってみる

私は冒頭で例に挙げた「世界一短い推理漫画」しか描いた経験がありません。誰かに原画を作ってもらうのでは時間がかかるし「棒人間」でごまかすのは面白くありません。自分でやってみると得られるものがあるかもしれないというので作ってみました。線画を作る処理に興味があったので、3DCGを基にします。手順は以下のとおりです。

ⅰ)原画を用意する
 3DCGソフトのDAZ Studioでキャラクターにポーズを取らせて画像を生成。これを画像処理ソフトのGimpで加工。

ⅱ)Powerpointのスライドショーを作る
 画像をスライドに貼り付け、吹き出しなどを付ける。2枚目以降のスライドの「画面切替え」を“プッシュ”にしておく。これは、スライド切替えのときに上から下へのスクロールのような効果を持たせるため。複数の吹き出しが同時に表示される版と、オーバーレイ方式で追加されていく版2つを作成した。

ⅲ)iSpring SuiteでHTML5変換 
 <PowerpointとiSpring Suiteでweb教材を作る>に紹介した方法でPlayerの設定をする。さらに、Playback and Navigationの設定に移り、矢印キーでスライド切替えができるように設定する。

ⅳ)webサイトにアップロード
 WordPressのプラグイン(Insert or Embed Articulate Content into WordPress)を使った。

結果について

作った「web4コマ漫画モドキ」を以下に示します。この投稿の冒頭で示したものと同じですが、ページ内のウインドウに表示されます。通常版は、1コマ分が一度に表示されます。一方、「オーバーレイ版」では、吹き出しが発言の順に現れ、コマが切り替えられるまでその場に残っています。次の吹き出しが現れると同時に消すというやり方も可能ですが、試してはいません。

サンプル(通常版)

サンプル(オーバーレイ版) 吹き出し発話順に出現していきます

結論は冒頭でまとめた通りです。補足します。

1)PowerpointとiSpring Suiteの現在の仕様では不十分
 問題と思われる点は以下のとおりです。

  • 画面切り替え
    前後(上下)のコマのつながりを意識させた画面切り替えが難しい。今回、スライド切替えの効果を“プッシュ”にすることで、縦スクロールのような雰囲気を持たせようとしているが、十分とは言えない。
  • スクロール
    画面を自由にスクロールできない。このため、複数のコマが同時に提示される漫画の「アナログ感」が乏しい。
  • 効果イフェクトを再現できないことがある
    Powerpointの効果をiSpring Suiteのスライドショー変換で再現できないことがある。今回わかったのはスライド切替え効果の“プッシュ”で、スライド前進のときは問題ないが後退のときにPowerpointアプリとは異なる挙動になる。他にも変換できない効果があるかもしれない。iSpring社のサポート担当に問い合わせてみるつもり。

2)新しいメディアとしての可能性
 絵、吹き出し、文章などを組み合わせた静止画をコマ単位で見せるだけでなく、これらの要素を出現・消滅や動きなどの効果を付けて表示できます。ウェブトゥーンではアニメーションを組み込んだ作品もあるそうですが、Powerpointでもアニメーションを組み込むことはできます。そのような作品はもはや「漫画」とは言えないものになるかもしれませんが、漫画から派生した新しい表現媒体になる可能性はあります。

今回は、プレゼンでよく使う「オーバーレイ方式」を吹き出しの表示に試してみました。でも余計なお世話という印象も持ちました。「漫画のリテラシーを身に付けさせる教材」に使えるかもしれませんが、そんなのいらない! と言われそうです。

3)その他
 今回注目したスライド切替えやスクロールとは別に、気が付いたことがあります。キャラクターは、手描きではなく3DCGソフトのDAZ Studioで作成したフォトリアルな画像を「漫画風」に加工して描きました。しかし、諧調が残っているので細部に注意が向いてしまい、漫画として読むときに邪魔に感じます。

実際、フォトリアルな画像に吹き出しを付けた作品がありますが、細部に注意が向くためなのか読みにくいと感じます。

漫画の、濃淡情報を省いた線画は、手抜きのために採用されたものかもしれませんが、結果としてセリフやストーリーを邪魔しない表現になっているのかもしれません。家電製品の取り扱い説明書でも、写真ではなくイラストを使った方が理解しやすいと言われています。このことは、スライドショーのデザインで一番大事だと思うポイント「注目させたい情報を制限する」と共通しています。

※画像の一部を変更しました(2024/07/29) 肌の塗りや髪の諧調を減らしています。個人的には変更後の方が好みです。

結局、ウェブトゥーンってどうなの?

ウェブトゥーンに関しては、漫画表現が縦長ディスプレイを持つスマホに合わせて変化して出てきたものです。なので靴に足を合わせるような無理な感じを受けてしまいます。視野が狭い縦長の世界に閉じ込められるようです。

一方、それは私が古い漫画のリテラシーしか持っていないから感じるものかもしれません。ウェブトゥーンの市場は拡大しつつあるようで漫画業界にとっては無視できない分野になりそうです。印刷技術などのハードウェアがコンテンツの創造や鑑賞のありかたに影響を与えることは、これまでも繰り返されてきました。web漫画の流れがどんな方向に向かっていくかは興味があります。

さて、今回の試みによって教材作成について何か得るところはあったのか?です。これは別の機会に譲りたいです(今回はそんなのばっかり・・・)。

]]>
https://tamlab.fc2.page/category-books/category-books-art/2329/feed/ 0
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/2118/ https://tamlab.fc2.page/category-materials/2118/#respond Wed, 22 Nov 2023 12:25:02 +0000 https://tamlab.fc2.page/?p=2118 二宮康明氏

11月20日の新聞で日本紙飛行機協会会長の二宮康明氏が逝去されたことを知りました。氏は,1967年から2016年まで月刊『子供の科学』で紙飛行機の付録を連載されていました。紙飛行機のパーツの形が印刷されたケント紙が折り込まれていて,パーツを切り離して貼り合わせると紙飛行機が完成するというものです。

氏の訃報に接して,昔作った「オードジャイロ型紙飛行機」のことを思い出しました。

「子供の科学」の紙飛行機と私

私は,小学校から中学校まで「子供の科学」を定期購入していたのですが,付録の紙飛行機を作ったことはほとんどありません。付録は「設計図」も兼ねるものと考えていました。残念なことに私は非常に不器用だったので,切り抜きや貼り合わせがうまくできずに,作品と設計図の両方を失ってしまうのが心配だったのです。

その代わり,ケント紙に鉛筆でパーツの型を描き切り抜いて紙飛行機を自作することは,よくやっていました。深く考えずに作図していたこともあり,良く飛ぶものは作れなかったと記憶しています。

オートジャイロの紙飛行機を作ってみた

あるとき(中学校の3年次か高校の1年次の頃?),「ちょっと変わった機体を作ってみよう」と考えて試してみたのが,オートジャイロの紙飛行機です。オートジャイロというのは,Photo. 1に示すような,ヘリコプターのような回転翼(ローター)を持つ飛行機です。ヘリコプターとは異なり,通常の飛行中はローターは動力から切り離されてフリーになっています。推進力は別の推進用プロペラで発生し,機体が前進して風を受けるとローターが回転して揚力を生み出します。

Photo. 1 小型のオートジャイロ 推進用のプロペラが後部に見える

二宮氏の紙飛行機の中には飾りのプロペラを持つ機体もあったと記憶しております。しかし,揚力を得るための回転翼を持つ紙飛行機に関しては,私は目にしたことがありません。「紙飛行機」の定義が,「全てのパーツが紙でてきていること」だとすると,紙だけで作るのは無理な「軸受け」を含むので,回転翼機は紙飛行機とは言えないかもしれません。しかし,面白そうだと思い,作ってみました。

作り方

フリーに動く回転翼を持つ,という程度の知識しか持っていない状態で作ったのが,以下の図(Fig.1~Fig.4)に示すような紙飛行機です。記憶があいまいだし記録も残っていないので,概略のみ示しています。パーツの形や数も覚えていません。問題の軸受けは,裁縫で使うマチ針とビーズを使っています。(ビーズは,竹ひごと紙で作るゴム動力の模型飛行機でも軸受けとして使われていたと思います。)

FIg1. パーツ一覧(紙の部品はもっと多い)        Fig.2 組み立て 

ケント紙のパーツを切り抜いたら,木工用ボンドで貼り合わせます(Fog.2)。ローターのパーツは切れ込みを入れ,回転方向側の縁の傾きが大きくなるようにして,断面が翼型になるように曲げておきます。

     Fig.3 ローターの取り付け           Fig.4 出来上がり

ローターの取り付け方の記憶があいまいです。たぶん,Fig.3のように,ローターをマチ針のヘッド部とビーズで挟むようにして針を刺しておき,木工用ボンドが乾ききらないうちに,本体の貼り合わせ部に差し込む・・・というやり方だったと思います。

飛ばしてみると

完成した機体を普通の紙飛行機のように手で投げると,ローターが回転しながら進んでいきます。飛行経路も,放物線というよりは若干直線に近いので,ローターが揚力を発生していることは確かです。しかし,よくできた普通の紙飛行機のように,上昇したり,旋回して長時間滞空しているということはありません。高度を下げながら滑空するだけだし,滞空時間も短いです。面白い飛び方はしないなあ,という感想を持ちました。

今調べてみると,実際のオートジャイロは,滑走距離が非常に短い,旋回半径が短い,失速しないなど,多くの特徴を持っていることがわかりました。ローターも,離陸時にはエンジンの動力を伝え,離陸後はクラッチを切ってフリーにするという機体もあったということです。

アニメーション映画の「ルパン3世 カリオストロの城」には「オートジャイロ」と呼ばれる飛行機が登場します。これは,機体の後部に推進用のプロペラが確認できますが,さらにローターの先端にジェットエンジンが付いているという奇妙な構造をしています。TVで観たときは「オートジャイロってローターは無動力のはずなのに・・・。」と思ったのですが,ローターを回転させる機体もあったことを今になって知りました。

もう一度作るなら

紙飛行機のオートジャイロはダイナミックな飛び方をしなかったこともあり,その後,作ることはありませんでした。でも,もう少し工夫すれば面白い玩具ができたかもしれないと思っています。例えば以下のようなことをしても面白かったかもしれません。

  • 材料を変える
    スチレンペーパーなど軽い材料を使う。軸受けに摩擦の小さいものを使う(ライトプレーン用のベアリングがあるみたい)。
  • ローターのブレードの数や傾きを変える
    ブレード(翼)を3枚や4枚にしてみる。また,回転軸を傾けるようにすると,旋回させることができたかもしれない。
  • 紐でひっぱる
    Fig.5のように,紐で引く力を推進力にする。

Fig.5 紐で引っ張るジャイロカイト紙飛行機

3番目の「紐で引っ張る凧のようなオートジャイロ」は,ジャイロカイトローターカイトと呼ばれているようです。潜水艦で引っ張る偵察用の無動力オートジャイロもあったそうです。

国際競技にもなっている紙飛行機では,推進力として手で投げるかゴムのカタパルトを使うことになっています。競技とは別に,凧のように紐を引きながら走ったり,向かい風の中で滞空させる形式の紙飛行機があってもよいのかな,と思いました。私が調べていないだけで,そのような玩具が世の中には出ているかもしれません。

]]>
https://tamlab.fc2.page/category-materials/2118/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
ドラッグ&ドロップで実行形式ファイルも添付可能なGmail用zipファイルを処理 https://tamlab.fc2.page/category-materials/category-materials-tools/1913/ https://tamlab.fc2.page/category-materials/category-materials-tools/1913/#respond Mon, 26 Jun 2023 07:56:11 +0000 https://tamlab.fc2.page/?p=1913

.exeや.vhdなどの拡張子を持つファイルをgmailに添付できるようにするWindowsアプリをPythonで作成してみました。gmailサーバが実行形式などのファイルの添付を拒否する問題はone driveを使うなどの方法で回避できます。ですから,このアプリにはそれほど需要があるとは思えません。しかし,ドラッグ&ドロップでの起動など,いろいろ勉強になったので,記録に残しておきます。

きっかけは,ある時期からVHDLのソースコード(拡張子は.vhd)をgmailの添付で送信できなくなったからです。中身はテキストデータなのに拡張子が.vhdになっているだけでgmailのシステムが添付を拒否してしまいます。(.vhdはVertual Hard Driveファイルの拡張子でもあるので,安全性を考えれば仕方がないことですが。)

VHDLソースコードをgmailで送受したい場合は,拡張子.vhdを.txtに書き直して送信し,受信したら拡張子を.vhdに戻せばよいのです。でもファイルの数が多くなると面倒なので,Pythonで拡張子変更とzip圧縮/解凍をするPythonコードを作ってみました。

そこで止めておけばいいものを,実行形式ファイルも送受できるコードにしてみました。Pythonのzip圧縮/解凍のコードの挙動やドラッグ&ドロップでの起動に興味があったからです。

注意:

<注意を読む>

この投稿は,十分にテストをしていないプログラムの開発過程の備忘録として書かれたものです。紹介されているコードにはバグや冗長な部分が含まれています。また,十分な例外処理を組み込んでいないので,重要なファイルの入っているフォルダを削除したり上書きしたりする可能性もあります。コードを使ったり参考にしたりする場合は注意をお願いします。

また,将来Pythonライブラリやgmailの仕様が変更されると機能しなくなる可能性もあります。

<閉じる>

使い方

実行ファイルは以下のボタンをクリックしてダウンロードしてください。

添付用フォルダを作る

空のフォルダを準備し,添付したいファイルを格納します。サブフォルダは作らないでください。

フォルダのアイコンを,gmailZippy.exeのアイコンにドラッグ&ドロップします。

ドラッグ&ドロップ

zipファイルができる

ドラッグしたフォルダと同じディレクトリに圧縮されたzipファイルが現れます。これをgmailに添付して送ります。

受信したファイルを解凍する

gmailで受信したzipファイルをgmailZippy.exeにドラッグ&ドロップします。解凍されたフォルダの中には,送信したファイルが格納されています。拡張子の書き替え/復元をしたファイルは,exe,vhdなどの名前のフォルダごとに整理されています。

zipファイルをD&D       →         復元された添付フォルダ

処理の流れ

送信側では空のフォルダを用意します。仮に名前をXXXとします。その中に送信したいファイルを格納します。

作成したコードは圧縮用と解凍用の2つの関数を持ち,圧縮/解凍と拡張子の書き換えを自動で行います。コードの処理の手順は以下のようになっています。

圧縮:

  1. フォルダXXXの中にある.exe,.vhdなどの拡張子を持つファイルの拡張子を.txtに変換しサブフォルダを作って整理
  2. フォルダ“XXX”をzip圧縮 → XXX.zipが生成される
  3. XXX.zipの拡張子を.zippyに変更してXXX.zippyにする
  4. XXX.zippyをフォルダに格納してzip圧縮 → XXX.zipが生成される

このXXX.zipをgmailに添付して送信します。

なお,上の1~4の操作をWindows OSを使った手動操作で行う場合は,XXX.zippyをフォルダに入れないでそのまま“右クリック>ZIPファイルに圧縮する”によりXXX.zipというファイルができ,これをzip展開すると,XXXフォルダの中にXXX.zippyが格納されたものが得られます。しかし,Pythonコードで実行する場合は,1度目の圧縮で生成されたXXX.zippyをフォルダに格納してから2度目の圧縮をする必要があります。

解凍:

  1. XXX.zipをzip解凍 → XXX.zipyが格納されたXXXフォルダが得られる
  2. XXX.zippyをXXX.zipにリネーム
  3. XXX.zipをzip解凍 → 解凍されたファイルを格納したフォルダXXXが得られる
  4. フォルダ内のファイルの拡張子を元に戻す

以上の圧縮と解凍の手順は手動で実行してもよいのです。なお,中間の圧縮ファイルの拡張子として.zippyを使っています。使われていない拡張子なら何でもよいと思いますが,確認はしていません。また,XXX.zippyをXXXにリネームしても動作するように思えますが,その場合の処理手順についても試しておりません。

Pythonコードの説明

ソースコードは1つのファイル“gmailZippy.py”にまとめられています。リストを上から順に説明していきます。

ライブラリ

ライブラリ宣言部分です。

# gmailZippy.py gmail添付用の拡張子変更・圧縮・解凍
# Change extension and compress/decompress to attach with gmail
# Copyright © 2023 Tamlab All rights reserved. ver. 1.0
from tkinter import filedialog
from tkinter import messagebox # for debugging
import os
import sys
import shutil
import glob
  • from tkinter import messagebox
    公開しているコードの中では使ってないが,デバッグの際にmessagebox関数を使用した。生成したファイルの上書や処理できないファイルの入力などの際の表示に使う予定。

拡張子書き換え

ファイルの拡張子を.txtに書き換える関数です。書き換えたファイルは書き換え前の拡張子の名前をつけたサブフォルダを作って,その中に格納します。

def ext_change(ext,temp_dir):
    # ext: file extention tring, temp_dir: working directory
    # 文字列extで与えられる拡張子を.txtに変更,Change the file extention to '.txt'
    path_name = temp_dir + '/*.' + ext
    files = glob.glob(path_name)
    if len(files) != 0: # If the corresponding file exists, do the following
        target_dir = temp_dir + '/' + ext
        os.makedirs(target_dir)  
        for file in files:  
            file_no_ext = os.path.splitext(os.path.basename(file))[0]
            name_dst = temp_dir + '/' + file_no_ext + '.txt'
            os.rename(file, name_dst) 
            shutil.copy(name_dst, target_dir) # ファイルを格納用フォルダに移動
            os.remove(name_dst)
  • def ext_change(ext,temp_dir):
     ext:拡張子の“.”を除いた文字列
     temp_dir:拡張子を書き換えたファイルを格納する一時フォルダの名前

拡張子の復元

拡張子を元に戻す関数です。解凍したフォルダに格納されているファイルの拡張子.txtを元のファイルの拡張子に戻します。

def ext_recovery(ext, folder_path): # 拡張子を復元, file extention recovery
    target_dir = folder_path + '/' + ext 
    path_name = target_dir + '/*'  # 対象とするフォルダ内の全てのファイル
    files = glob.glob(path_name)
    for file in files:    
        file_no_ext = os.path.splitext(os.path.basename(file))[0]
        name_dst = target_dir + '/' + file_no_ext + '.' + ext
        os.rename(file, name_dst) 
  • def ext_recovery(ext, folder_path):
     ext:戻したい拡張子の“.”を除いた文字列
     folder_path:拡張子を書き換えるファイルを格納しているフォルダの名前
  • path_name = target_dir + ‘/*’
    files = glob.glob(path_name)
     target_dirはフォルダパスの文字列。この文字列を”XXX””とすると,path_nameは”XXX/*”となる。次のglob関数でフォルダXXXの下にある全てのファイルのリストが得られる。

圧縮

フォルダを圧縮する関数です。まずext_change関数でフォルダ内のファイルの拡張子を書き換え,次に圧縮します。圧縮では,まずzip圧縮し,得られた圧縮ファイルの拡張子を.zipから書き換え,再度zip圧縮しています。

zip圧縮を2回実行するのですが,2回目の圧縮で,ちょっとひっかりました。

Windows OSの操作ではXXX.zippyをzip圧縮すると,XXX.zipというファイルができます。これを解凍するとXXX.zippyを格納したXXXフォルダが得られます。

ところが,Pythonコードでshutil.make_archiveを使うと,2回目の圧縮ではXXX.zippy.zipというファイルができますが,中身の情報が失われています。そこで,XXX.zippyをXXXフォルダに格納したものを作り,これをshutil.make_archiveで圧縮しています。

def compress(path_name):
    # 非圧縮状態のフォルダを指定,Specify a object folder in uncompressed state.
    file_dir = path_name
    file_name = os.path.splitext(os.path.basename(path_name))[0]
    os.chdir(file_dir) # Sets the current directory to the specified folder.
    os.chdir('../')
    target_dir = os.getcwd()

    temp_dir = file_dir + '/temp' # Create temporary directory.
    if os.path.isdir(temp_dir):
       shutil.rmtree(temp_dir)
    shutil.copytree(file_dir , temp_dir) # Copy all files to temp_dir. 

    ext_change('vhd', temp_dir) # Change file extentions to '.txt'.
    ext_change('exe', temp_dir)
    ext_change('py', temp_dir)

    # フォルダをzipにアーカイブ, Archive folder to zip file.
    # First compression
    shutil.make_archive(temp_dir, format='zip', root_dir=temp_dir)
    # Rename .zip -> .zippy
    temp_name = temp_dir + '.zip'
    temp_name_zippy = temp_name + 'py'
    os.rename(temp_name, temp_name_zippy)

    # Renew temp directory
    shutil.rmtree(temp_dir)
    os.mkdir(temp_dir)

    # Copy the .zippy file to temp directory
    temp_dir_file = temp_dir + '/temp.zippy'
    shutil.copyfile(temp_name_zippy, temp_dir_file)   

    # Second compression
    shutil.make_archive(temp_dir, format='zip',root_dir=temp_dir)

    # Copy the compressed folder to destination directory.
    dst_name = file_name + '.zip'
    shutil.copy(temp_name, dst_name)
    
    # 一時フォルダの削除
    os.remove(temp_name)
    shutil.rmtree(temp_dir)
    os.remove(temp_name_zippy)
  • ext_change(‘vhd’, temp_dir)
    ext_change(‘exe’, temp_dir)
    ext_change(‘py’, temp_dir)
     書き直す拡張子は,現在,”.vhd”,”.exe”,”.py”の3種類。.pyは書き直さなくても大丈夫だと思うが,サブフォルダに整理する機能を利用するため使っている。将来,対応したい拡張子が増えた場合は,この関数を追加していけばよい。
  • shutil.make_archive(temp_dir, format=’zip’, root_dir=temp_dir)
     1回目のzip圧縮。
  • shutil.rmtree(temp_dir)
    os.mkdir(temp_dir)
     作業ディレクトリを一旦,中のファイルと共に削除し,その後で同じ名前のディレクトリを作っている。
  • temp_dir_file = temp_dir + ‘/temp.zippy’
    shutil.copyfile(temp_name_zippy, temp_dir_file)
     空になっている作業用ディレクトリに.zippyファイルを格納。
  • shutil.make_archive(temp_dir, format=’zip’,root_dir=temp_dir)
     2回目のzip圧縮。

解凍

解凍するための関数です。zip展開し,得られたファイルの拡張子を.zippyから.zipに戻し2度目のzip展開をして,最後にファイルの拡張子を元に戻します。

def decompress(path):
    # Decompress the zippy archive.
    fileName = path
    # Get the string of the file name without the file extension.
    folder_path = fileName.replace('.zip', '')
    if os.path.exists(folder_path):
        folder_path = filedialog.askdirectory(
            title = "Specify a folder.",
            initialdir = dir) 
    # zipファイルを展開,Unzip the file.
    zippy_path = folder_path + '/temp.zippy'
    zip_path = folder_path + '/temp.zip'
    shutil.unpack_archive(fileName, folder_path) # 1回目の展開, first extraction
    os.rename(zippy_path, zip_path)
    shutil.unpack_archive(zip_path,folder_path)  # 2回目の展開, second extraction
    os.remove(zip_path)  # 一時フォルダの削除, Remove temp folder. 
    ext_recovery('vhd', folder_path) # File extention recovery.
    ext_recovery('exe', folder_path)
    ext_recovery('py', folder_path)
  • folder_path = fileName.replace(‘.zip’, ”)
    if os.path.exists(folder_path):
    folder_path = filedialog.askdirectory(
    title = “Specify a folder.”,
    initialdir = dir)
     zip展開された後に得られる“XXX”と同じ名前のフォルダが存在する場合は,フォルダ名を改めて指定するようにしている。
  • shutil.unpack_archive(fileName, folder_path)
    os.rename(zippy_path, zip_path)
    shutil.unpack_archive(zip_path,folder_path)
     zipファイルを展開し,拡張子を.zippyから.zipに戻してから2度目のzip展開をしている。
  • ext_recovery(‘vhd’, folder_path)
    ext_recovery(‘exe’, folder_path)
    ext_recovery(‘py’, folder_path)
     拡張子を復元する関数。復元したい拡張子の数だけ関数を呼び出す。

main関数

main関数です。引数path_nameでフォルダまたはzipファイルのパスを指定します。パスがフォルダのものであればcompress関数を,zipファイルのものであればdecompress関数を呼び出します。

def main(path_name):
    if os.path.isdir(path_name):
        compress(path_name)
    else :
        path_ext = os.path.splitext(path_name)[1]
        if path_ext == '.zip':
            decompress(path_name) 

ドラッグ&ドロップで起動する

main関数を呼び出す部分です。これにより,フォルダやファイルのアイコンをドラッグ&ドロップすることでアプリケーションを起動するようになっています。

if __name__ == '__main__': # If the script is activated as main,
    # sys.argv[1] is the dragged and dropped path name.
    main(sys.argv[1])
  • if __name__ == ‘__main__’:
     変数__name__ は,このPythonコードがメインとして起動されたとき’__main__’という文字列になる。つまり,Pythonコードがメインとして起動されたときに,ifブロック内の式文が実行される。
  • main( sys.argv[1])
     フォルダやファイルをアプリのアイコンにドラッグ&ドロップして起動すると,sys.argv[1]に そのファイルやフォルダのパスが格納される。このパスを引数としてmain関数を呼び出す。

<VSCodeでのデバッグのときは・・・>

コードの開発はVisual Studio Codeを使っています。デバッグモードで実行させることで,設定したブレークポイントの位置で変数の値などをチェックできます。しかし,ドラッグ&ドロップで実行させると,VSCodeを介さず実行させるらしく,ブレークポイントが機能しません。それでデバッグ中は,main関数の部分を,以下のように変更しています。

  • if __name__ == __main__’:
    arg_d = ‘aaa・・・a’
    main(arg_d)
     文字列aaa・・・aには動作テストに使うフォルダやzipファイルのパス文字列を記しておきます。

私が知らないだけで,VSCodeを使ってドロップ&ドロップで起動するコードをデバッグする方法はあるのかもしれません。

<閉じる>

コードの全体

改めてコード全体のリストを示します。

# gmailZippy.py gmail添付用の拡張子変更・圧縮・解凍
# Change extension and compress/decompress to attach with gmail
# Copyright © 2023 Tamlab All rights reserved. ver. 1.0
from tkinter import filedialog
from tkinter import messagebox # for debugging
import os
import sys
import shutil
import glob

def ext_change(ext,temp_dir):
    # ext: file extention tring, temp_dir: working directory
    # 文字列extで与えられる拡張子を.txtに変更,Change the file extention to '.txt'
    path_name = temp_dir + '/*.' + ext
    files = glob.glob(path_name)
    if len(files) != 0: # If the corresponding file exists, do the following
        target_dir = temp_dir + '/' + ext
        os.makedirs(target_dir)  
        for file in files:  
            file_no_ext = os.path.splitext(os.path.basename(file))[0]
            name_dst = temp_dir + '/' + file_no_ext + '.txt'
            os.rename(file, name_dst) 
            shutil.copy(name_dst, target_dir) # ファイルを格納用フォルダに移動
            os.remove(name_dst)

def ext_recovery(ext, folder_path): # 拡張子を復元, file extention recovery
    target_dir = folder_path + '/' + ext 
    path_name = target_dir + '/*'  # 対象とするフォルダ内の全てのファイル
    files = glob.glob(path_name)
    for file in files:    
        file_no_ext = os.path.splitext(os.path.basename(file))[0]
        name_dst = target_dir + '/' + file_no_ext + '.' + ext
        os.rename(file, name_dst) 

def compress(path_name):
    # 非圧縮状態のフォルダを指定,Specify a object folder in uncompressed state.
    file_dir = path_name
    file_name = os.path.splitext(os.path.basename(path_name))[0]
    os.chdir(file_dir) # Sets the current directory to the specified folder.
    os.chdir('../')
    target_dir = os.getcwd()

    temp_dir = file_dir + '/temp' # Create temporary directory.
    if os.path.isdir(temp_dir):
       shutil.rmtree(temp_dir)
    shutil.copytree(file_dir , temp_dir) # Copy all files to temp_dir. 

    ext_change('vhd', temp_dir) # Change file extentions to '.txt'.
    ext_change('exe', temp_dir)
    ext_change('py', temp_dir)

    # フォルダをzipにアーカイブ, Archive folder to zip file.
    # First compression
    shutil.make_archive(temp_dir, format='zip', root_dir=temp_dir)
    # Rename .zip -> .zippy
    temp_name = temp_dir + '.zip'
    temp_name_zippy = temp_name + 'py'
    os.rename(temp_name, temp_name_zippy)

    # Renew temp directory
    shutil.rmtree(temp_dir)
    os.mkdir(temp_dir)

    # Copy the .zippy file to temp directory
    temp_dir_file = temp_dir + '/temp.zippy'
    shutil.copyfile(temp_name_zippy, temp_dir_file)   

    # Second compression
    shutil.make_archive(temp_dir, format='zip',root_dir=temp_dir)

    # Copy the compressed folder to destination directory.
    dst_name = file_name + '.zip'
    shutil.copy(temp_name, dst_name)
    
    # 一時フォルダの削除
    os.remove(temp_name)
    shutil.rmtree(temp_dir)
    os.remove(temp_name_zippy)

def decompress(path):
    # Decompress the zippy archive.
    fileName = path
    # Get the string of the file name without the file extension.
    folder_path = fileName.replace('.zip', '')
    if os.path.exists(folder_path):
        folder_path = filedialog.askdirectory(
            title = "Specify a folder.",
            initialdir = dir) 
    # zipファイルを展開,Unzip the file.
    zippy_path = folder_path + '/temp.zippy'
    zip_path = folder_path + '/temp.zip'
    shutil.unpack_archive(fileName, folder_path) # 1回目の展開, first extraction
    os.rename(zippy_path, zip_path)
    shutil.unpack_archive(zip_path,folder_path)  # 2回目の展開, second extraction
    os.remove(zip_path)  # 一時フォルダの削除, Remove temp folder. 
    ext_recovery('vhd', folder_path) # File extention recovery.
    ext_recovery('exe', folder_path)
    ext_recovery('py', folder_path)

def main(path_name):
    if os.path.isdir(path_name):
        compress(path_name)
    else :
        path_ext = os.path.splitext(path_name)[1]
        if path_ext == '.zip':
            decompress(path_name) 
            
if __name__ == '__main__': # If the script is activated as main,
    # sys.argv[1] is the dragged and dropped path name.
    main(sys.argv[1])

使ってみた感想

圧縮のときに,動作が遅いように感じます。2回の圧縮の間に実行しているファイルの削除やコピーなどの手間が解凍処理のときよりも多いためかもしれません。コードも圧縮処理の方がゴチャゴチャしています。時間があれば見直したいです。

圧縮したファイルをgmailに添付すると,アップロードの際,プログレッシブバーの表示があるところで停止して,しばし“考え込んで”いるような挙動を示します。gmailが添付可能なファイルを判断するアルゴリズムが不明なのですが,何やら怪しんでいるような雰囲気です。

実際にファイルをgmailで他のPCとやりとりしてみると,実行形式ファイルも送受できています。しかし,他のPCでは解凍の際と解凍した実行ファイルを実行させる際にWindowsのセキュリティシステムが警告を表示してきます。開発に使ったPCでは,そのような警告は出てきません。

さて,このコードで気になるのは安全性の問題です。zip圧縮は自己解凍の機能が無いはずだし,受け手側で幾つかのステップの処理をするので,比較的安全だと考えています。しかし,自己解凍や自動起動の機能を持つ悪意のあるプログラムの送受に利用できてしまうことは,問題かもしれません。

実際にgmailでのファイル添付に使う場合は,用途を教育用のコードにやり取りに限って利用していただくのが適切だろうと考えています。

]]>
https://tamlab.fc2.page/category-materials/category-materials-tools/1913/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