memorandums

日々の作業ログです。

コーンフレーク。。。じゃない。テキストファイルから会話音声ファイルを生成するスクリプトを書いたったん

背景

偉い先生と一緒に担当している科目がありまして、前半は偉い先生、そして後半は僕なんですが。

4、5週間後くらい始まるので少しずつ準備を進めています。

で、とある回のスライドで以下のようなものを作りました。

たまにありますよね?理系の本で、先生と生徒が会話しているわざとらしいやつ。あれです。作っているうちにLINEに似せようと変に懲りだして。。。

f:id:ke_takahashi:20200605122028p:plain

これ僕が読むんじゃなくて、機械に読ませたら面白いんじゃね?と思ったわけです。

まぁ、暇です。

macならsayコマンドで簡単に喋らせることができます。便利ですよねぇ。ほんと。

qiita.com

で、この適当なスクリプトファイルを作って1行ずつ読み込んではsayコマンドをsystemコマンドで実行すればいいんじゃね?と誰でも思いつきますね。

で、それをサクっとやってみたわけです。こんな感じです。10分もかかりません。

なんか面白くなったので、どうせなら、ただしゃべるだけでなく音声ファイルにできたらあとでいろいろ利用できるよなぁ。。。って思ったんですね。

で、sayコマンドにoオプションがあるのでやってみたのですが、それだと1行ずつ音声ファイルができちゃうんですね。。。

たくさん生成された音声ファイルをあとで連結するコマンドがあれば、それをRubyで呼び出せばいけそうですね、と発想しました。

で、ぐぐった。

あった。

soxというコマンドです。偉い誰かさんが作ってくれたようです。超有名なffmpegでもできるんですがコマンドの指定方法が面倒なので却下しました。

以下のコマンドを叩くだけでインストールが完了します。これもmacのいいところです。

brew install sox

ちなみにsoxを知ったのは、このページでした。
stackoverflow.com

この段階のコードもすぐにつぶやきました。#つぶやきRubyってハッシュタグをつけました。これは「つぶやきProcessing」という活動を真似っ子しただけです。流行ってくれるといいな。。。と思うのです。最近のPythonブームで影が薄くなっていますから。。。Ruby界隈の偉い人が取り上げてくれたらなぁ。。。

で、ついでだから、Githubにコード載せて、デモとして面白みがでるように、これまた有名なコーンフレーク漫才?のスクリプトこちらのサイトからいただきました。

本題

で、完成したのが以下でーす。

会話文を書いたテキストファイルを引数で与えるとmp3に変換してくれます。

github.com

soundcloud.com

話者が単一のTTSはたくさんあると思いますが、会話させることを前提にしたアプリってあまりみたことがないように思います。意外と使えるんじゃないかな。。。って思います。

こんなことをやっているから貴重なやる気のある午前中がなくなっていきます。。。まぁ、いいですよね。余白は大事です。

では、また!

講義メモの提出方法についてバタバタしたこと

背景

いつものように長い背景からはじめます。

昨年から担当している講義科目で授業終わりに「講義メモ」を提出してもらっています。

目的は「人の話を聞きながらその場で情報をまとめる」というどの社会人にも必要とされるスキルが身につけるのでは?という期待が1つと「授業中に寝させない」という後ろ向きな目的もあります。

以下は昨年のある科目の提出物です。赤いところは学生さんの番号と氏名が記入されていましたが消しています。

f:id:ke_takahashi:20200604172104j:plain

情報学科なのに手書きかよ。。。と思われるかもしれませんが、手書きのよさは今でも(たぶん未来でも)ありますよね。我々がアナログな存在だからです。見聞きした言葉や記号を書きながら(描きながら)脳内を整理していく感じです。考えたことや感じたこともそこにどんどん書き込んでいきます。

もちろん、デジタルのツールやアプリを使う方法もありますし、そこは否定していません。ノートPCを持ち込んでいる人は別の方法でメモしても構わないとは伝えていますので。

ちなみに、昔、講義ノートを提出してもらって評価していたこともありますが、講義資料にかいてあることをただうつすだけの作業になり。。。なんか学校みたい。。。って感じですよね?大学とは思えませんが。。。労力もかかり。。。効果が感じられず1年で断念しました。

memorandums.hatenablog.com

一方、この講義メモは個人的にはとてもうまく行っていると実感しています。

学生さんからの授業アンケートでも「役立った」「よかった」「今後も他の科目でも使いたい」といった肯定的な意見が多くを占めていました。

講義資料に同じようなことが書いてあるのに、同じことをまた自分の手で書く意味があるのか。。。と疑問に思う人もいるにはいます。でも、やってみると(振り返ってみると)、よかったと思えるのではないかと思います。

さて、せっかくの個人的なベストプラクティス?ですので、非常勤先の授業にも展開しようと考えました。

コピー用紙を授業はじめに配布して記入して提出してもらう感じです。

問題

ただ。。。今年はオンライン授業にしたため。。。講義メモの回収をどうするか。。。悩みました。

大学であれば、Googleアカウントがありますし、今年はGoogle Classroomも導入していますので、資料の配布や提出物の回収で悩むことはありません。

非常勤先では、ご自身のスマホでクイズに回答してもらったりはしてきました。

しかし、教室でその場で写真を撮って、それをどこかにアップロードする。。。アカウントなしで。。。実現できるサービスを探さなければなりませんでした。

解決方法1(30days Album)

写真共有サービスのうち、利用者登録も必要とせず、特定のアプリのインストールも必要とせず、簡単に写真をアップロードする手段としてあれこれ探した結果、ペポパさんの以下のサービスが残りました。

30d.jp

私がユーザ登録(無料)し、アルバムを作成しました(無料でも150枚までは写真を追加できるようですので十分です)。

学生さんに、mailto:アルバムの投稿用URL、というリンクを踏んでもらうことで、メーラーが起動してスマホで撮影した写真を投稿できることを確認しました。

本日、非常勤の授業初日でしたが、授業開始1時間くらい前に再度の確認のため、私のスマホAndroid)で動作することを確認し、娘のスマホiphone)で確認しようとすると。。。iPhoneの画面でメールアカウントの登録画面が表示されました。。。「えっ?なんでメールアカウントを全く登録していないの?」と聞くと「メールなんて使わないし」と瞬殺。。。キャリアメールくらいは登録されていると期待した私がバカでした。

大慌てで、代替手段を探しはじめました。

解決方法2(GASを使った独自アップロードページ)

この非常勤先の授業では、教室で講義を数回おこなったあと、演習室でPCを使った演習を行います。

PCで課題を提出するためGASでファイルアップロードページを作っていました。

これを講義メモの提出に使うことにしました。

理想をいえば、ファイル名を個人名などに書き換えて欲しかったですが、それを強制するのは難しそうです。

紙に番号と氏名がありますので、写真ファイルさえしっかりアップロードしてもらえばあとで視認で仕分けることができそうです。

AndroidiPhoneでアップロードできることを確認しました。

ふーう。。。授業開始30分前。

とりあえずGoogle Driveを開いて、アップロードしたファイルをクリックすると。。。なぜか開けない。jpegって書いてあるのに対応したアプリがないとか怒られます。

しかたがなく、ローカルにダウンロードしてプレビューで見ようとしたのですが同じく見れない。。。まさか。

ぐぐってみました「GAS ファイルアップロード 破損」と。ビンゴでした。

qiita.com

対策も書かれていたので助かりました。新しいV8エンジンにバグがあるんだとか。。。古いエンジンに戻して公開しなおせば破損の症状はなくなりました。

とりあえず、動作確認ご、Androidスマホで一通り操作するところをキャプチャして操作説明動画を作り、授業資料に貼り付けました。

授業開始5分前くらい。。。何とか間に合いました。

結果

授業はZoomを使ったオンライン授業でした。いろいろとありましたが講義終了の10分前に一区切りつけ、講義メモの提出作業にはいってもらいました。

教室で他の先生や事務の方がサポートしてくれたこともあり、5分くらいで全員の写真がアップロード完了しました。

2重クリックの防止機構を実装していないので同じ写真が5,6枚アップロードされている人もいました。

複数人がimage.jpegという同じファイル名でアップロードしていたようですが、ファイル名が同じでもGoogle Driveでは上書きしないので(上書きするようにもできますが)、とりあえず紛失がないように設定しておいたおかげで、全員分のデータを手に入れることができました。

めでたしめでたし。

来週まで2重クリック防止機構くらいは実装しておきたいな。。。と思います。

間に合ってよかったよかった。

GASを新規作成するときにデフォルトアカウントとは違うアカウントのドライブに保存したいときに

背景

さっさと結論を見たい方は本題へどうぞ。

長ったらしいタイトルですいません。。。将来の自分がググったときに見つけやすいように思いつく限りの言葉を並べてみました。

今、ずーっと使っている個人のGoogleアカウントと、職場でGmailを採用するようになってから提供されたGoogleアカウントの2つを使っています。

職場の方のアカウントはG Suite対応なので、Google Driveの容量制限などがなく利点が多いのですが、やはり個人のアカウントでアクセスしたいWebサービスもあるので、デフォルトは個人の方にしてきました。

一時期、Google Chromeにプロファイルを2つ作り、デフォルトを切り替えられるようにしていたのですが、いろいろ不便なことがあったので、個人のアカウントをデフォルトにしています。その辺について書いたのが以下のエントリーです。

memorandums.hatenablog.com

で、今日、作業をしていて新たな問題が。

職場のGoogle Driveをメインに使用しているのですが、そこでGAS(Google App Script)を新規作成するとどういうわけか、個人のGoogleアカウントで作成しようとするんです。他のドキュメントやスプレッドシートGoogle Driveのアカウントで作成してくれるのに。。。です。

これがどうやっても変更できない。。。想像では、GASはアカウントの認証があって初めて実行できるものですから、使用者が同一であっても、アカウントに紐つけたい。。。気持ち(設計思想)はわからないくはないです。

なぜ、職場のアカウントで新規作成したのに個人のアカウントに切り替えようとするのか?

最初、Google App Script自体が個人のアカウントで入れたものだからか。。。と思い、一度、アンインストールしてインストールし直しました。。。でも変わらず。

結局、Google のデフォルトのアカウントで作成しょうとする、というだけのことでした。

では、デフォルトのアカウントを切り替えるには?と考えるわけですが、方法としては、すべてのアカウントからログアウトする必要があるんですね。。。これは面倒。なぜできないのかは理解できません。

で、試したのが以下の方法です。

本題

繰り返しになりますが、

Google DriveからGoogle App Scriptを新規作成するときには、Google Driveのアカウントに関わらず、Googleのデフォルトアカウントで作成されてしまうようです。

対策:シークレットウィンドウを使います。

すべてログアウトすると、元に戻るには、すべてログインしなおさなければなりません。

そんなことをするくらいなら、シークレットウィンドウを開いて、そこで職場のGoogle アカウントでログインし、Google DriveでGASのスクリプトを新規作成すればいいってことです。

用が済んだら、シークレットウィンドウを閉じて、個人のGoogleアカウントをデフォにした状態で、職場のアカウントで開いたGoogle Driveから、さきほど新規作成したGASを開けばいいわけです。

ただ、このときには、やはりデフォルトアカウントでGASを開きにいこうとしますので、下図の赤丸のところをクリックして職場のGoogleアカウントに切り替えれば、アクセス権云々を許可するとかの必要はありません。

f:id:ke_takahashi:20200601093537p:plain

なんか、もっといい方法ないんですかね。。。体験的に解決した1つの方法ですが。。。

GASでCSVファイルをダウンロードするスクリプトが動作しなくなった(Google Chrome 83)

一ヶ月ほど前に、ボランティアで学内向けのツールを作ったんですが、昨日、「CSVファイルがダウンロードできません」という報告がありました。

memorandums.hatenablog.com

実行してみると、コンソールに以下のメッセージが表示されていました。

Download is disallowed. The frame initiating or instantiating the download is sandboxed, but the flag ‘allow-downloads’ is not set. See https://www.chromestatus.com/feature/5706745674465280 for more details.

うーん。わからん。とりあえずこのメッセージでググってみました。そこで見つかったのが以下の書き込み。

stackoverflow.com

Google Chromeがバージョンアップしたことがまず要因であることがわかりました。

その改修の中で、上のワーニングメッセージにあるリンクに書かれているように、iframeタグを使用して読み込まれたスクリプトでダウンロードが使用不可となったようで、それを許可するには、‘allow-downloads’ってやつをセットせい、と書かれているようです。

自分ではiframeというタグを書いた記憶がないのと、そもそもiframeは昔々に使った程度で今も使われているんだ!という驚きもあったりでした。そのくらいの知識しかなかったということです。

確かに、GASで生成したHTMLソースにiframeタグが挿入されています。GASを実行しhtmlファイルを詠み込む処理をしていますので、そのときに実行しているのかなぁ。。。理解が乏しい状況です。

<iframe id="sandboxFrame" allow="accelerometer *; ambient-light-sensor *; autoplay *; camera *; encrypted-media *; fullscreen *; geolocation *; gyroscope *; magnetometer *; microphone *; midi *; payment *; picture-in-picture *; speaker *; usb *; vibrate *; vr *" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-top-navigation">
</iframe>

恐らく、Google Chromeのコンソールに表示されたメッセージを解釈すると、この(↑)iframeタグの属性に「allow-downloads」を含まれる操作ができればいいんでしょうね。GASで。その方法をGoogleが作ってくれることを祈りますが。。。

issue trackerにも追加されているようです。ウォッチしていくしかないですかね。。。これが売り物だったら。。。ちょっと青くなりますね。。。GASは便利だけど、Google次第ってところがありますから。。。

https://issuetracker.google.com/issues/157368720

ちょっとロング・ウォーキング ⇒ 結果的に20kmだった。。。

昨日はいい天気でしたね。


家族はそれぞれ用事があったので、ぷらっと歩きに行ってきました。


週中は昼間に自宅で仕事・授業をして夕方に気晴らしに近所を5kmくらい歩いています。


太宰府天満宮まで歩くこともたまにありますがそれでも10kmくらいです。


もう少し遠くに行って試してみようと思い、Google Mapで探すと油山展望台が良さそう。


片道12kmと書かれていましたので往復24km。とりあえずの挑戦としてはありかと思いました。


スマホは電池が持ちそうになかったので、昔買った携帯ラジオを片手に子供電話相談室を聞きながら行ってきました😁


天気が本当によくて気温も真夏ほどではなかったので歩きやすかったです。


ただ、日差しが強かったので(日焼け止めも塗らずに外出したため)、日焼けが酷かったですが。。。


道中はほぼ町中なのでトイレの心配も装備の心配もない感じでした。


水や食料の確保も持参する必要はなくオンデマンドで入手できます。


ちょうど疲れたところで和菓子屋があり「水ようかん」という文字に惹かれて店内に。聞くと水ようかんは品切れとか。。。とりあえず以下を購入してパクリ。うま。

f:id:ke_takahashi:20200525130820j:plain


流れを抑えるためのブロック?ですね。。。これは意図的に設計されたものなんですよね。。。土木的な知見がないとわからなそう。。。後で調べようと思いパチリしました。まだ調べていません。

f:id:ke_takahashi:20200524122811j:plain


空腹になったらコンビニでおにぎりを買って近くのベンチで食べたり。


油山に登るにはちょっとした坂道になっています。


道端に綺麗な花が。

f:id:ke_takahashi:20200524141031j:plain


油山の入り口に「油山」のいわれが書かれた看板がありました。大和時代ですからね。。。僕の地元の北海道では、あっても江戸か明治とかですね。

f:id:ke_takahashi:20200524141422j:plain


ちょっといくととこんな看板も。山頭火さんです。

f:id:ke_takahashi:20200524141545j:plain


油山観音の入り口。とても歴史を感じます。

f:id:ke_takahashi:20200524141557j:plain

f:id:ke_takahashi:20200524141809j:plain


観音様をみたあと、さらに奥に行くと、ひばり観音と。まさかと思ったらやはり美空ひばりさんでした。

f:id:ke_takahashi:20200524143137j:plain


展望台につくとこんな感じでした。人が多かった。。。やばいでしょう?

f:id:ke_takahashi:20200524145325j:plain

f:id:ke_takahashi:20200524145329j:plain

f:id:ke_takahashi:20200524145333j:plain


11時に出発して、展望台についたのは14時半くらいでした。3時間くらいかかりました。


3密状態だったので、早々と帰路に。


帰りは違う道を通ろうとして、住宅街をさまようことになりました(汗)。


自宅に戻ったのは18時くらいでした。


靴がきつかったのか、足の親指の爪が両方ともブス色になっていました。


腰痛持ちなので、ぎっくり腰になりかけそうなのと戦いながらなんとか騙し騙し帰宅しました。


結果、歩いたのは20km。。。いつか100kmウォーキングにでたいのですが。。。この状態ではほど遠いです。


体力回復したらまた挑戦します。

zoomでレコーディングして変換途中で失敗したら(mac編)

学内のslackに「変換中に次の講義が始まってしまい動画変換に失敗した」という情報が挙げられていました。

ググると以下のzoom公式の情報が見つかります。

support.zoom.us

この操作でもダメな場合もあるようで、その場合はzoomにファイルを送ると変換のお手伝いをしてくれるようです。いたつくです。

僕はまだその経験がないのですが、もし、そうなったらどうしよう。。。と時間があったので再現して試してみました。再現には、変換途中でzoomプロセスをkill -9しました。

意外と変換が速いので長めに録画して何度か挑戦しました。変換途中の残骸?ファイルが以下です。

f:id:ke_takahashi:20200523113350p:plain

これを確認したあと、zoomアプリを普通に起動すると、初回だけ「未変換ファイルがあるので変換しますか?」みたいなメッセージが表示されます。そこで変換することもできます。

ただ、その後は表示されないので、以下の操作を手動でする必要があります。

f:id:ke_takahashi:20200523113503p:plain

といっても、難しいことは何もなく、①〜③の順に選択するだけでOKです。

今回のテストケースについては、無事、変換が正常終了しました。

この辺はカッチリ作っているのでしょうね。

ライブを録画して公開したいときに、実は記録が失敗していた。。。はなかなか訴訟ものかと思いますので。

とりあえず、人のふりみて我が振りなおせ(いや、調べてみました)的な記事でした。

では。

今日も福岡はいい天気です。

ちなみに、以下は数日前に歩いたときにパチリした写真です。いい季節ですね。

f:id:ke_takahashi:20200523113810j:plain

土曜ですが授業が2コマあります。

終わったらYouTubeで筋トレしてウォーキングしよっと。

背景差分で動画にちょっとしたエフェクト?をつけてみた

リモートワークしながらYouTube Musicを聴いていました。

たまたま聴いたのが以下の曲。ちょっと生っぽい音源で気になったので映像を観てみるとこれがまたカッコいい。

www.youtube.com

何回か観ているうちに、骨格抽出してCGモデルとか動かせたらカッコいいだろうな。。。と。

ただ、動画像から簡単に抽出するライブラリはまだないようだったので、とりあえずProcessingのOpenCVライブラリで何かしようかとサンプルをいじいじ。

いじいじした動画は、自分のPC上でQuickTimeを使って画面録画しました。音声はなしで。

とりあえず、できたのが以下でした。

www.youtube.com

ほとんどサンプルと同じですが、コードも置いておきます。

import gab.opencv.*;
import java.awt.Rectangle;
import processing.video.*;

OpenCV opencv;
Movie video;
Rectangle[] faces;

void setup() {
  size(1164, 324);
  video = new Movie(this, "twice.mov");
  opencv = new OpenCV(this, 1164, 324);
  opencv.startBackgroundSubtraction(5, 3, 0.5);
  video.loop();
  video.play();  
  colorMode(HSB);
}

void movieEvent(Movie m) {
  m.read();
}

void draw() {
  background(0);
  image(video, 0, 0); 
  opencv.loadImage(video);
  opencv.updateBackground();
  opencv.dilate();
  opencv.erode();

noStroke();
  for (Contour contour : opencv.findContours()) {
    if (contour.area() > 1000) {
      Rectangle a = contour.getBoundingBox();
      fill(random(255),255,255,80);
//      circle(a.x+a.width/2, a.y+a.height/2, 50);
      rect(a.x, a.y, a.width, a.height);
    }
  }
}

かっこいいですね。