VimとRubyでScrapboxの日記に追記する

前回書いたTipsに近い話ですが。

note103.hateblo.jp

もっと手軽にVimから投稿できないかなあ、と思って作った物をご紹介します。このとき、投稿対象はその日の日記ページとします。個別のScrapboxページを作成する場合には、前回の記事後半に示した「Vimから新規ページを作ってコピペする」を使います。

目次

VimコマンドラインモードからScrapboxに投稿する

まずはVimコマンドラインモードからサクッと投稿するやつ。

https://i.gyazo.com/455a334df1d3ee3a738cda41f74a6b2a.gif

.vimrcに設定した任意のマッピングを打つと、コマンドラインに以下が表示され、テキストの待機状態になります。

:!ruby ~/scrapbox/sb-post.rb note103 (ここにテキストを入れていく)

上記のデモでは、テキスト部分に「Vimから投稿テスト」と入れています。このとき、行頭に全角スペースを入れていますが、それは投稿時に1字下げするためです。この字下げを半角スペースでやると自動的に詰められてしまうので、全角にしています。

コードを見てみましょう。まず .vimrcの方ではこのように書いています。

nnoremap <Leader><Leader>i :<C-u>!ruby ~/scrapbox/sb-post.rb note103 

1行ですね。ここではリーダーキー(ぼくはスペースにしていますが)を2回叩いてから、iを1回打つとこの待機状態に入るようにしています。

後半の「note103」とある部分は、Scrapboxのプロジェクト名です。複数の投稿先候補がある場合には、一番使うプロジェクト名をここに書いておいて、それ以外のプロジェクトに投稿したい場合には、コマンドラインに出てきてからさらっと書き換えるようにしています。

コードが1行で済んでいるのは、大半の(というかすべての)処理をRubyスクリプト(sb-post.rb)に任せているからですね。

では、そのRubyのコードを見てみましょう・・とするつもりでしたが、じつはこのスクリプトは後述の機能も兼ねているので、そこまで紹介してからあらためて掲示します。

Vimで書いている任意の内容をそのまま投稿する

コマンドラインモードから投稿できるのはお手軽ですが、上記のとおり、半角スペースを含む投稿はできなかったり、けっこう制約があります。

そこで、バッファに書いている内容の一部をサクッと投稿(日記に追記)できないか、と考えて作ったのが以下です。

まずは使ってる様子を見てみましょう。

カーソルが乗っている行を投稿

https://i.gyazo.com/a426037e4f782604e22ec86bd70414a0.gif

選択範囲を投稿

https://i.gyazo.com/73ee490c9f98e2c6bbcc2b55cf3c3bcf.gif

前回の記事で紹介した、バッファの内容をScrapboxにコピペするものはバッファ全体を対象としていていましたが、今回は「カーソルが乗っている行」、または「選択した部分」だけを飛ばしてくれます。またこの際には、最初に書いたとおり、投稿先は当日の日記を対象にしています。

コード

では、それを実現しているコードを示します。まずは .vimrcに記載している関数&マッピングがこちら。
gist.github.com

その中から呼び出しているRubyのコードはこちら。
gist.github.com

どちらもエスケープの置換が泥縄な気がしますが・・とりあえずこれである程度は機能してくれます。

近況

最近はPerlの次に勉強する言語として、なるべくRubyを使ってみるようにしています。まだまだRuby本来のパワーとか独自性などの魅力には触れられていない自覚ですが、それでもいろいろ直感的に使える*1感じがして、面白いです。使い方がわからないときも、ちょっと検索すればたくさんの情報に出会うことができます。

余談ですが、Rubyの方の22行目にある & の置換について、他と同様にバックスラッシュでエスケープしようとしても効いてくれなくて、しばらくハマりました。検索を繰り返してもなかなか解決策に出会えず・・諦めかけましたが、以下でようやく解決しました。
stackoverflow.com

先日のbuilderscon 2018では最終日のスピーカーだったAmyさんが、その発表の中で「大抵の疑問はStack Overflowで解決する」と言っていましたが、まったくその通りだなと思いました。

さらにちなみに、そのQ&Aの中ではビシッと解答が示された後にも質問者が「これじゃ動かないよ」と言っていて、解答者がそれに対して「そりゃエスケープの問題じゃなくて君のコードの問題だよ」と言っているのを見て、ああ、いかにも初心者のハマり方・・わかります・・という感じでした。

初心者は2重、3重に少しずつ間違えているので、そのうち一つの問題を解消しても不具合は直らず、正しい修正の正しさがわからない、何をどう間違えているのかもわからない、そして混沌に至る・・というよくあるパターン。でもそれも、結局はひとつずつ地道に解消していくしかないんですね。それが一番の近道というか、舗装された安全な道。

そうした地道な一歩一歩をくり返す中で、徐々にスピードが上がったり、効率的な進み方を思いついたりするのかなあ、と思っています。

*1:構文をちゃんと覚えてなくても、「こんな感じかな?」とかカンでやってみるとそれで動いたり。

最近のScrapboxの使い方

Scrapbox、最近はほとんど毎日使っています。日記的に使うことが多いですが、メールやブログの下書きに使うことも多いです。

いろんなことがScrapbox周りで効率化している気がするので、現在の使い方について記録しておきたいと思います。

目次

家族との情報共有

他人と使うケースとしては、最近では家族との情報共有で利用することが多いです。家族とは基本的にはチャットでやり取りしていますが、たとえば買物リストとか、旅行の持ち物リストや旅券に関わる各種の情報(予約番号とか)のように、何度も見直したり、チャットで流れないでほしかったりするデータはWikiのような静的な場所を使った方が良いので、そういう用途で使います。

ちなみに、以前はこれをサイボウズLiveでやっていました(が、すでに同サービスは終了)。最近Kibelaを使ってみたところ、フイフイ動くしかわいいし、家族との用途ならKibelaもいい気がしましたが(たしか5人ぐらいまでは無料だし)、非営利でプライベート用途のための料金プランをより切実に想定しているのは、Scrapboxの方かなという気もします。それに、家族はMarkdown記法にこだわったりもしないですからね・・。

仕事関連でも時々使いますが(フリーランス・ユーザーなので無料プラン)、これはたぶん本来の用途に比べてちょっと特殊で、ほとんど更新しているのはぼくだけ、というパターンが多いですね。自分が頭の中だけで覚えておくのはちょっと大変だな、というネタを入れておいたりします。ただいずれにしても、現時点では仕事での使用頻度はそれほど多くありません。

メールやブログの下書き

個人用途で言うと、最初にも書いたようにメールやブログの下書きでよく使います。と言っても、ちょっとした内容なら、そもそも下書きなんてしないわけですが、けっこう大事なメールとか、込み入った内容の場合には、ぼくは3段階ぐらいに分けてそれを書くので、そういうときに役立ちます。

第1段階では一気にラフを書き切ります。この時はサクッと手元で起動できるVimを使うことが多いですが、いずれにせよディテールや整合性などは気にせず、とにかく頭にある情報を出し切ることが優先です。

それが終わったらScrapboxにコピペして、一旦そこから離れます。いわゆる「寝かせる」状態。

しばらくしたら、その第1段階で作ったものをScrapbox上で直していきます。これが文章を作る上では一番大事な工程です。
もし分量が多く、PCを使えるならVimに持っていくこともありますが、「修正」という点に重きを置くならばやはりScrapboxが有効です。

なぜなら、Scrapboxには「編集画面」がないからですね。編集するための画面が、すなわちそのまま「閲覧画面」になっています。
これ、本当に革命的にすごいことです。Googleドキュメントもそうじゃないか、と思われるかもしれないですが、Googleドキュメントをモバイルから操作しようとすると、編集画面に移動するためにエンピツアイコンをクリックしなければいけません。そして、Scrapboxにはそれがありません。

その話からもわかるとおり、この第2工程の修正作業では、ぼくはPCとモバイルを行ったり来たりします。文章を書く際、というより直す際に、「この表現、変だな」とか「ここタイポじゃん」とか気づきやすいのはどういう時かというと、それは「無責任な人」になったときです。よくTwitterで的外れなリプライをしている人がいますが、そのぐらい無責任な態度で文章を読むと、見方がものすごく厳しく、かつシンプルになり、「こんな言い方じゃわからないでしょ(このオレ様が)」といった具合に様々な指摘が頭の中に浮かんできます。

このような「無責任な人」になるスイッチというか、トリガーになるのが、たとえば上記のような「時間を置く(寝かせる)こと」だったり、あるいは「環境を変えること」だったりします。
そして、その「環境を変える」方法の中でもとくに手っ取り早いのが、「PCとモバイルを行ったり来たりする」ことです。

ぼくの場合、あまり寝かせてる時間がないようなときは、PCで書いた文章をすぐに手元のiPhoneで読んだり、iPhoneで書いた内容をPCで見直したりします。そうすると、だいぶ見え方が新鮮になって、変な言い回しやわかりづらい表現に気づきやすくなります。別人のような目で読み返すことができます。

見つけた間違いを修正するときも、Scrapboxに文章を入れておくと、たとえば今までPCで書いたり読んだりしていた文章を、iPhoneSafariから、新鮮な目のまま(間違いに気づいたときのイメージを保持したまま)修正していくことができます。Scrapboxでは閲覧画面から編集画面に移動する時間が存在しないので、画面遷移の際に微妙なニュアンスを忘れてしまうようなこともなく、感覚的にサッサと直していくことができます。

このような感じで、ちょっと込み入った文章を直すときにはScrapboxをよく使っています。

ターミナルやVimからページを生成

Scrapboxでは、URLにあたる文字列を操作することで、任意のページを自在に生成することができます。

詳しくは以下で説明されていますが、

scrapbox.io

この機能を利用して、当日または任意の日付の日記ページをターミナルからさくっと生成したり、Vimで書いた文章を少ない手数で新規ページにコピペしたりできるようにしています。

ターミナルから日記ページを作る

まず、ターミナルからどのような操作をしているか紹介します。

ぼくは.bashrcに以下のような関数を入れています。

gist.github.com

おそらくプロの人から見れば冗長で、恥ずかしい気もしますが、やりたいことは充分に実現してくれます。

論より証拠ということで、この最後にあるエイリアス「sddn」を使って、ぼくの公開Scrapboxであるところの「https://scrapbox.io/note103/」に本日付(2018/08/31)の日記ページを作ってみましょう。

gyazo.com

できましたね。個人的には、前後の日付(8/30と9/1)が入るようにしているところがポイントです。

コードの中で、言語設定を英語にしたり日本語にしたりしていますが(L7, 33)、これは曜日を英語にするための処理なので、ターミナルの設定によっては不要かもしれません。
(ぼくの環境では、これがないと「#Friday」のところが「#金曜日」になります)

また、openコマンドを使っているので、Mac以外のOSだとそのままは使えないと思います。

if文の中の処理は、当日の日記ではない、任意の日付のページを作りたいときのためのものです。
例として、「2016/01/01」の日記を作ってみましょう。

gyazo.com

できました。ちなみに、この本文の内容は、上記のヘルプにもありますが、中身を全部URLに入れ込むことにより生成しています。

また、エイリアスの設定からもわかりますが、「sddn」というコマンドの実体は「sbdd」という関数にプロジェクト名の「note103」という引数を渡しているだけです。よって、その引数を変えればどのようなプロジェクトにも応用できます。

なお、このようにいろいろデフォルト情報(前後の日付や各種タグなど)が詰まった新規ページではなく、当日または任意の日付の日記にアクセスしたいだけ、というときには、以下の関数を利用しています。

function sbd {
    local site="$1"
    if [ -z "$site" ]; then return; fi

    local ymd=$(date +%Y-%m-%d)
    if [ ! -z "$2" ]; then
        ymd="$2"
    fi
    open "https://scrapbox.io/$site/$ymd"
}

alias sdn="sbd note103"

たとえば、すでにその日の日記を作成済みの状態で、さっきの日記ページ作成コマンドを使うと、初期設定用のタグや前後の日付がまた入ってしまうので、単にその日記を閲覧したいだけであれば、これを使うのがシンプルです。

Vimから新規ページを作ってコピペする

もう一つ、時々使うのが「いまVimで書いてる文章をそのままScrapboxにコピペする」というワザです。前半で触れた、Vimで書いたメールのドラフトをScrapboxに持っていくときなどによく使います。

先にコードを示しておくと、.vimrcに以下の関数およびマッピングを書いておきます。

gist.github.com

では、動かしてみましょう。

gyazo.com

はい。GIF動画だと順番がわかりづらいですが、

1)最初に2行だけ文が入ったVimがあって、
2)ファイル下部のコマンドラインで対話型の操作をすると、
3)一意の日時から生成されたScrapboxの新規ページが現れて(この時点では新規ページはカラ)、
4)そこにクリップボード上にコピーされていたテキスト情報をペースト。
5)で、最後に少し内容が変わった最初のVimが出てきています。

この最後の段階でわかりますが、上記2)と3)の間で元ファイルの1行目に目的地のURLが追加されています。それを使って、Vimプラグインの「open-browser.vim」でジャンプすると自動的にページが生成される、という仕組みです。

github.com

なお、Vimでヤンク(コピー)したテキストは、デフォルトではMacクリップボードには入らないので、これについては別途設定が必要になります。

ぼくの場合は、以下のような設定が.vimrcに入っていました。(これを書きながら久しぶりに思い出しました)*1

" Use clipboard register.
if has('clipboard')
  if has('unnamedplus')
     set clipboard& clipboard+=unnamedplus
  else
     set clipboard& clipboard+=unnamed
  endif
endif

この方法の良いところは、新規ページを作成しても、中身が自動的にリンク先にペーストされないことです。

え、自動で反映されたほうが便利じゃないの? と思われるかもしれませんが、Vimで書いている内容ってけっこうセンシティブなものが多いので、万が一ペースト先のプロジェクトが間違っていたら大変なことになります。(いわゆる誤爆

その点、この方法だとまずは新規ページが作成されるだけで、それまでVimで書いていた中身は、まだクリップボードに保持している状態なので、行き先のプロジェクトが目当てのものであることをきちんと確認してから、安心してペーストすることができます。

・・といっても、じつはそれはあくまで結果論で、初めは自動的にペーストできるものを作ろうとしていたのですが、URLに中身を持たせる方法だと文章量に制限があるようで、長文には向かなそうだったので、結果的にこの方法に落ち着いています。

まとめ

ということで、最近のScrapboxの使い方を簡単にまとめてみました。

Scrapboxはウラではいろいろ複雑な処理やコードが動いているのだろうと思いますが、ユーザーからすればシンプル極まりない構造になっているので、そのぶん使い方も無限大というか、自由度が高くて面白いです。

また、前半にも書いたように、編集画面と閲覧画面の切り替えがないことの優位性はいくら強調してもしすぎることはないでしょう。

今後どのような新たな使い方が出てくるのか、思いつくのか、ということも楽しみです。

*1:ヤンクとクリップボードの設定についてはこれだけでは解消できない場合もあるかもしれません。もしハマったら検索で最新情報にあたってください。

YAPC::Okinawa 2018 の思い出

もう半年前になってしまいますが、3月に行われたYAPC::Okinawa 2018 ONNASONについて。

yapcjapan.org

本イベントについては、すでに2本の記事を書きましたが、

YAPC::Okinawa 2018 ONNASONの記録 - the code to rock
ノンプログラマーのプログラミング活用法 - the code to rock

まだもう1本分、これも書いておかないと・・と思っていたトピックがいくつかあったので、少し時間ができたこのタイミングで一気に書いておきたいと思います。

目次

前夜祭

まずは本番前日の前夜祭。国際通りに面したビルの5階、結婚式の2次会などもできそうなステキ・スペースで開催されました。

当日の様子はこの辺りから。
30d.jp

この前夜祭、リジェクトコン*1とその後のLTから構成されていて、ぼくは翌日の本編で発表する予定だったので、この日はただボーッと見ているだけのお客さんとして参加するつもりだったのですが、会場に着いてみると思っていた以上に知らない人が多い・・時々、Perl入学式つながりで知っている人や、過去のYAPCでお会いした人とは挨拶できましたが、でもやっぱり基本、知らない人ばかり。

んー、これ、このままじー・・っと2時間ぐらいチビチビお酒を飲みながらここで過ごすの、ちょっとつらいかも・・? と思い、急遽ホテルまでMacを取りに戻って、LTに参加させてもらうことにしました。
LTに参加することで、自分が誰なのか、少なからぬ人に周知できると思ったし、時間を持て余すこともなくなるし、と。

しかし、この中途半端な野心がその後のスタッフの皆さんの仕事を増やすことになってしまい・・。というのも、いざLTとなって接続を始めたらなかなかモニターにスライドが反映されない。押しても引いてもまったく駄目。それも、順番が最後から2番めぐらいだったので会場の皆さんは注目しているし、うわー、ミスった、すみません! もうやめます! ゴメンナサイ、じゃあ次の方! とか泣きそうになりながらその場で撤退宣言をくり返していたのですが、現場を仕切っていた id:codehex さんをはじめスタッフの皆さんが「いや、大丈夫ですよ」と優しく&粘り強くセッティングを手伝ってくれて、最後には id:karupanerura さんが「これでどうだ」とセットしてくれたのが届いてようやくスタート。

いやあ・・本当に皆さんの胆力というか、タフさ、落ち着き、頼りがい、どれを取っても見習いたいです・・ありがとうございました。とくに codehex さんの「大丈夫ですっ」には救われました。

後から思いましたが、ぼくが翌日の本編で、そこそこリラックスして、緊張しすぎずに発表できたのは、この前夜祭でもうこれ以上ないぐらいのテンパりを体験したからだと思っています。いくら何が起きるかわからない本編だとしても、上記以上の失敗というか、焦った雰囲気はもうナイだろう、と思ったので。実際、本編の方でもスタッフの皆さんにたくさん助けて頂いて、まったく滞りなく進めることができました。

さて、そんな経緯で行ったLTですが、ぼくがその会場で何かしら見るに足る、価値と言えるものを提供できるとしたら、とりあえず自動文字起こしの実演ぐらいかな・・と思ってそれをやりました*2。実際には、そのようにして起こした文字をVimからtextlintを呼び出して自動校正する、というところまでやりたかったのですが、これはtextlintが動かない・・というのを2〜3回繰り返したところで時間切れ。

まあ、文字起こしの方ではけっこう良い反応を頂いて、最後の時間切れのところでも「ああ〜、残念〜!」という雰囲気を会場で一体になって醸せた感じもするので、自分としてはOKというか、充分な成果だったかなと思っています。

ちなみに、そのときにtextlintでやりたかったのはこんな感じのことでした。(当日使う予定だったテキストで再現)

youtu.be

このワーッと滝のように流れていくのが、純粋に面白いなって。それを最後に見てもらったら最初のドタバタもチャラにできるかな・・と思っていましたが、そうも行かなかったのが自分らしいなと思っております。*3

あらためまして、当日サポートしてくださったスタッフの皆さん、そして声を上げて反応してくれた会場の皆さん、ありがとうございました。

当日(私信)

その翌日、本編の出来事に関しては上記2本のブログでだいぶ書いていますが、これまでに書きそびれたことをひとつだけ。というのは、じつはぼくの発表会場では、以前にPerl入学式のサポーターとしてご活躍&サポーターチャットの方でも時々やり取りしていた id:gomayumax さんが部屋付きのスタッフ業をされていて、でもぼくは初対面だったのでそれがgomaさんだとは結局最後まで気づかなかったんですよね〜・・。まったくまともな挨拶もせず、失礼しました・・😅また次の機会に。

ハッカソン

そのさらに翌日、3/4にはPerlハッカーの@skajiさんの呼びかけで、以下のハッカソンが催されました。
connpass.com

ぼくはもちろん(というか)こういった会にはこれまで縁がなかったのですが、今回はスピーカーでしたし、エイヤという感じで申し込みまして、参加してきました。

前日の本編後の懇親会(というか飲み会)にけっこう遅くまで出ていたので、この日は二日酔いがけっこうキツかったですね・・。

しかしその懇親会で、「いやー、明日ハッカンソン行くんですけど、やることなくて・・とりあえず雰囲気だけ味わいに行きます」みたいなことを言っていたら、 @charsbarさんから「そんなこと言わないで〜。何かできることあるんじゃないの?」とニコニコしながら突っ込まれてしまい、んー、たしかに。何かあるかなあ・・できること・・と考えていましたが、とりあえず編集者を名乗ってはいるので、じゃあPerl関連のドキュメントでも何かしら見て回って、直せそうなところがあれば手を入れてみるか・・と。

それで、会場に着いてからさっそくperldoc.jpを見に行って、何かできることはあるかな・・とひとしきり周遊。
japan.perlassociation.org

ハッカソン会場にもいらしていた @charsbar さんから、修正依頼を送るならどうするのか、などもその場で教えてもらって*4、しかしこれ、実際に何かやるとなると、ただ頭から見ていくより自分の使っているモジュールなどから見た方がいいか・・とか、けっこう砂漠に水をまくような大変さを実感。

ということで、これはこれとして見るとして、他に何かないものか・・と思っているところで、ふと、少し前に目にしたJPAさん(Japan Perl Association)のWebサイトで、部分的に情報が古かったりリンク切れになったりしているところがあったのを思い出したので、そちらの修正作業をやることに。

幸い、ハッカソン会場にはJPA理事の@karupaneruraさんもいらしていたので、さっそくその旨相談。数秒で「じゃあ、この時間だけ編集権限を渡しますよ」と決定、すぐにアカウント手続き、1分後にはもうページの編集ができる状態になっていました。すご・・。

あまりのスピード感に眩暈を覚えつつ、ざくざく作業。まずは修正対象になりそうなページや箇所をリストアップ。
要修正の箇所と、要検討につき修正案を提案するまでにする項目に分けていきます。

その後は要修正のところからどんどん修正。終わったら修正内容を簡単にGistにまとめて @karupanerura さんに共有。というあたりまで行ったところで、終了30分前ぐらい。あっという間!&なにこの充実感!

タイポやリンク切れの箇所についてはここで示してもあまり意味がないので、わかりやすい成果をひとつだけ。

以下の、Perlの参考文献を示したページで『初めてのPerl』と『続・初めてのPerl』のリンク先が古い版だったので、最新版に差し替え。
japan.perlassociation.org

初めてのPerl 第7版

初めてのPerl 第7版

続・初めてのPerl 改訂第2版

続・初めてのPerl 改訂第2版

その他、参考書籍として深沢千尋さんの『かんたんPerl』と木本裕紀さんの『業務に役立つPerl』も追加してはどうか? と提案しておきました。

かんたん Perl (プログラミングの教科書)

かんたん Perl (プログラミングの教科書)

もっと自在にサーバを使い倒す 業務に役立つPerl (Software Design plus)

もっと自在にサーバを使い倒す 業務に役立つPerl (Software Design plus)

ゆるくて鋭い突っ込みと具体的なレクチャーで作業を促してくださった@charsbarさん、前夜祭に続き圧倒的なパフォーマンスで段取りを組んでくださった@karupaneruraさん、そしてこのような機会を作ってくださった@skajiさん、ありがとうございました。

焼肉

エンジニアといえば焼肉ですが、この日の打ち上げ(?)も焼肉でした。たしか会場は以下。

焼肉酒場 牛恋 那覇松山店(那覇/焼肉) - ぐるなび

入ってすぐ、店員さんから「牛恋(うしこい)は初めてですか!?」と前のめりに聞かれて(たぶん決まり文句)、「あ、はい・・」という感じになったのが記憶に深く残っています。

参加者は錚々たる面々。上記の方々に加え、本編の最後にキーノートを務められた@yappoさん、同じくPerl Mongerとして著名な@xaicronさん、そしてもう何年も前、YAPC::Asiaのリジェクトコンのときに「malaさんですか?」と話しかけて以来のmalaさん等々。

とにかくハイレベルなプログラマーが集まっているので何を話しているのか理解できない時間の方が長かった気がしますが、まさにそういった時間を体験することこそがこのハッカソンに参加した目的でもあったわけで、考えてみるとその念願はこの焼肉会でようやく達成されたのかもしれません。

個人的には、@xaicronさんに以下の記事およびそれを含むテスト系のアドベントカレンダーがすごい参考になって何度も見直している、という話をできたこと、そしてそれを書いた頃にはどういう動機でどんな感じで書いていたのか、みたいなことをお聞きできたのは嬉しかったですね。
perl-users.jp

ちなみに、Perlアドベントカレンダーのテストの話といえば、@myfinderさんの以下もそれはもう何度も読んでいます。

Test::Moreでテスト事始め - JPerl Advent Calendar 2009

お土産屋めぐり

最終日。3/3が本編だったので、翌3/4に沖縄を発った人も多かったようですが、ぼくは上記のハッカソンに参加したので帰りはこの日、3/5でした。

結果的には、このスケジュールがYAPC恒例の飛行機ガチャにつながって、出発時刻は延期につぐ延期、もう今日はダメか・・? という頃になってようやく&突然飛んで、しかし羽田に着いた頃にはもう終電が自宅まで届いてなくて、たしか蒲田あたりに一泊するはめになったりしましたね・・。

しかしそこから少しだけ時計を巻き戻して、飛行機の予定は夕方だったので、ひとまず午前および昼過ぎまではようやくの&今回初の沖縄観光。

といっても、それほど時間があるわけでもないので、とりあえず那覇の公設市場で本場の沖縄そばを食べて、残った時間でお土産を買って帰ろう、という算段でした。

沖縄そばをどこで食べるか? については、さとなおさんこと佐藤尚之さんの以下を参考にしました。

沖縄の行った店リスト(170店)|さとなおのおいしいスペシャル

さとなおさんはぼくがかつて震災ボランティアを手伝っているときに、団体は違ったもののそのご活躍を見ながら、「ああ、すごい人だな」と思っていた方。信頼のおける、尊敬できる人。その人が沖縄のあちこちを食べ歩いて、ちょうどぼくが今回歩き回れる範囲も上のページでいろいろレポート(&レート付け)してくれています。ありがたい!

ということで、まずは目当てにしていた公設市場から。1階には生鮮食品のお店がすごい勢いで営業を繰り広げていますが、その2階には飲食店街が広がっています。そこをぐるりと一周した後、さとなおさんのレポートを読みながら「ここがいいかな」と思ったところに入店・・したつもりが、いきなり失敗。じつはこの飲食店街、店同士がびっしり並んでいるのですが、その店の境界が非常にわかりづらい。それで、目当てにしていた店に入ったつもりが、隣の店の席に着いて注文してしまいました。

f:id:note103:20180305120535j:plain:w300
(絵に描いたような普通の沖縄そば

いや、普通に全然おいしそうだし、実際「こんな感じだと思ってた」という味だったし、値段も量もほとんど隣と変わらないので、不満ということではないものの、それでもいきなりつまづいた〜・・という感じ。

幸い、このときには半量で頼んでいました。かなりレアな機会ということもあり、そば一杯で終わるつもりはなかったので・・。それで、ふたたびさとなおさんのレポートを見ながらあちこち歩いてみたところ・・出会ったお店がこちら、「牧志そば」。

f:id:note103:20180305134319j:plain:w300

ちょっと見づらいですが、ドアに「ソーキそば専門店」と謳っています(肉筆で)。そしてさっきの店よりずっと安い。さらには(たしか)100円追加で沖縄名物の炊き込みご飯「じゅーしー」も食べられる。ということでそれらを注文。

f:id:note103:20180305132328j:plain:w300
(目が覚めるほどシンプルなソーキそば)

f:id:note103:20180305132713j:plain:w300
(じゅーしー。普通にボリュームある)

んー、おいしい!(ガッツポーズ)
やっぱりさっきの市場のお店、悪くはなかったけど、いわゆる観光客向けの感じだったのかな・・と思ってしまうほどこちらの店は大満足。諦めなくてよかった・・。

気を良くして、しばらく散歩。

f:id:note103:20180305122010j:plain:w300
(パラソル通り、と言うらしい)

f:id:note103:20180305123323j:plain:w300
(でかい)

f:id:note103:20180305124525j:plain:w300
(ぐっと来る)

f:id:note103:20180305124558j:plain:w300
(猫)

f:id:note103:20180305145634j:plain:w300
(ちんすこうはここで。@国際通り

こんな感じでグルグル回ってから、ふたたび公設市場に戻ってきたところでなんだか気になるお店を発見。たぶんこちら。
www.satoukibikotobuki.com

さとうきびジュース・・。ここで飲まなかったら後悔しそう、と思っておそるおそる注文。すると、いきなりナタみたいなものでバシン、バシン、とご主人がさとうきびをタテに割り始めて、おもむろに鉛筆削りみたいなジューサーに投入。これ、すごいな・・と思って許可を取って撮らせて頂きました。

youtu.be
(マシンの下の方にコップがセットされていて、そこにジュースが入る感じみたい)

できあがり。

f:id:note103:20180305153326j:plain:w300

そのまま店内の小机を借りて飲み干しました(持ち歩いたらゴミの処分に悩みそうだったので)。けっこうスッキリした甘さでおいしかったです。

そろそろこの辺で疲れてきたので、お土産探しモードに切り替え。先ほど、公設市場の食堂階で1枚のチラシを見かけて、「ゆっくる」という観光案内所が紹介されていたので、とりあえずそこへ向かいます。

machigwa.info

アーケード街の並びにある小さなスペースですが、スタッフさんたちがいろいろ優しく教えてくれました。この辺でお土産屋は・・? と聞くと、すぐ近くにある「てんぶす那覇」というビルの1階に、この案内所の姉妹店のような感じで「ショップなは」という店が入ってるので、そこに行ってみたら、とのこと。

ショップなは

このてんぶす那覇、ものすごい穴場スポット。1階には休憩できるスペースもあるし、トイレも観光案内所もある。国際通り近辺というのは、なにげに「ちょっと休める場所」というのがないので、これはすごい助かりました。ということで、お土産を探す前にしばらく休憩・・。

の後、お土産物色。店員さんにあれこれと相談。「こういうのありますか?」「こういうのは?」と。家族へのお土産はもちろん、個人的にはなんと言っても古酒。「古酒ってあまり馴染みがないんですが、どういうのがオススメでしょう?」と聞いて、教えてもらったのがこちら。

www.koosugura.jp

たしか30度で720cc。1800円前後。初めてだったらこれがオススメ、と。折しもキャンペーン的なタイミングで、寝かせる前のそれ(いわゆる泡盛)も小瓶でついてくるという。嬉しい!&即決。

トータルでたぶん20分ぐらい、あーでもないこーでもない、とじっくり選んで、ようやく会計。そして自宅への郵送もお願いしました。

とにかく先ほどのまちぐゎー案内所「ゆっくる」にしても、この「ショップなは」にしても、まったく何も知らない状態で那覇情報を知りたかったら最適の場所だと思いました。

とくに「ゆっくる」の方では「沖縄そばめぐり」のマップなどももらって、うわー、今さら! 最初にここに来ればよかった・・と激しく後悔しましたね・・。まずはこちらの案内所を訪ねて、全体を把握してから各所をめぐれば効率が良かったなあ、と思いました。というか、次行くことがあったらそうします。

飛行機ガチャ

上にも少し書きましたが、YAPC名物の飛行機ガチャも体験できました。この日は夕方ぐらいから雨が降り出して、一気に豪雨になり、しかし東京はさらにすごかったらしくて「羽田に着陸できないから」という理由でなかなか飛べなかったようです。

個人的には、その那覇の豪雨が地味にキツくて、靴も靴下もビショ濡れ・・替えの靴下は預けた荷物に入れてしまっていて、売店で靴下、いやせめてビーチサンダルでもないかと探しましたがそういうのも無く・・。けっこう心身ともにダメージを受けました。

そんな中、ヤケになって買った紅いもソフト。少しはストレスも軽減された気がします。

f:id:note103:20180305192207j:plain:w300

結局、飛行機は21時過ぎぐらいに那覇を飛び発ち、しかし自宅までは間に合わなかったので都内で一泊して、3/6に帰り着きました。
おつかれ!!

終わりに

ということで、記録しそびれていたあれこれをまとめて書いてみました。これで思い残すこともなく、ぼくのYAPC::Okinawaを終わらせることができます。(おそい)

三度、精神的にも肉体的にもタフなスタッフの皆さん、そして誰も彼もめっちゃ優しくてすぐに友達みたいになってしまった沖縄の皆さん、楽しい旅をありがとうございました! また行きたい!!

*1:本編に届かなかった方による発表イベント。

*2:これについては他の記事で詳しくやっているので割愛。以右などご参照のほど。21世紀の文字起こし(2) - the code to rock

*3:しかしこれ、自覚的には当日とまったく同じ環境で再現したら成功したんだけど・・どうして現場で駄目だったのかいまだにわからず。

*4:管理されている@argrathさんは前日のうちに、これまた @charsbar さんからご紹介頂いていました。

bashでコマンドライン引数を複数取得する方法を間違えて認識していた

これまで気づかなかった方が不思議なのだけど、ハマって解決したのでメモ。

.bashrcに自作関数fooを作って、ターミナルから呼び出す際に、引数としてbar1, bar2, bar3を入れるという場合。

受け取り側では、今までこんな感じで関数を書いていた。

function foo {
    local arg="$@"
    echo $arg
}

これでこのように呼び出すと、

$ foo bar1 bar2 bar3

このように出る。

bar1 bar2 bar3

だから当然、この中のbar2を取り出したいと思ったら、関数にはこのように書けばいいのだと思っていたけど、

function foo {
    local arg="$@"
    echo ${arg[1]}
}

実行しても何も出てこない。

おかしい・・と思ってこのようにプリントデバッグしてみると、

function foo {
    local arg="$@"
    echo all: ${arg[@]}
    echo arg0: ${arg[0]}
    echo arg1: ${arg[1]}
}

こんな感じ。

all: bar1 bar2 bar3
arg0: bar1 bar2 bar3
arg1:

ひとつ目の要素に全部入ってるんですね〜・・😇

で、しばらくハマっていましたが、関数内での引数の受取り時に、配列として受け取ればよかったようです。

function foo {
    local arg=("$@") # <= New!
    echo all: ${arg[@]}
    echo arg0: ${arg[0]}
    echo arg1: ${arg[1]}
}

実行。

all: bar1 bar2 bar3
arg0: bar1
arg1: bar2

OK!

んーむ、引数を用いた.bashrc内の関数、少なくとも3年は使ってきた(書いてきた)と思うんだけど、全然気づいてなかった・・。

同じ変数名でなくてもいい部分を同じ変数名にして初心者を混乱させてしまう現象

最近はProgateでRubyを中心にいろいろ未知の言語に触れている。

prog-8.com

Progate、控えめに言ってグレイトすぎるサービス。勉強法についてこれほど意識的なプログラミング学習サービスを他に知らない。「回転」や「定着」や「インセンティブ(ごほうび)」みたいなものが学習者にもたらす効果について、これ以上なくストイックに追求していると思う。リスペクト。

さてしかし、そのProgateのRuby学習コースIVの中で、ありがちな不備というか、これはまったくProgateに限ったことではないのだけど、すでにプログラミングをよく理解している人が初心者に教えたり入門書を書いたりするときにやりがちな悪例(というか推奨できない教え方)を目にしたので、記しておきたい。

具体的には、上記コースの「インスタンスメソッド」の項をやっているときに、こんなサンプルコードに出会った。(部分的に編集して抜粋)

class Menu
  attr_accessor :name
  attr_accessor :price
  
  def initialize(name:, price:)
    self.name = name
    self.price = price
  end
  
  def info
    return "#{self.name} #{self.price}"
  end
end

menu1 = Menu.new(name:"すし", price:1000)

puts menu1.info

実行すると、こんな。

すし 1000円

なんの変哲もない、シンプルなサンプルコードのように見えるけど、じつはここには初心者を混乱に陥れる問題が潜んでいる。

それはどこか? たとえば、ここ。

  def initialize(name:, price:)
    self.name = name
    self.price = price
  end

「name」と「price」という変数名が、本来必要な数に比べて多すぎる。

その言い方でわかりづらければ、以下の修正例を見てほしい。

  def initialize(name_foo:, price_bar:)
    self.name = name_foo
    self.price = price_bar
  end

先ほどはnameとpriceが3個ずつ出てきたが、ここでは1個ずつあるだけで、あとはname_fooとprice_barに置き換えられている。

さて、ここでは一体、何を直したのだろうか? それは、

  • 同じ変数名じゃなくてもいい部分にも同じ変数名を使っている

という状況を、

  • 同じ変数名じゃなきゃいけないところだけを同じ変数名にしている

という状況に変えた、ということになる。

最初のサンプルコードでは、「本来なら別の変数名でも構わないところ」が、なぜか(たぶん何となく)「同じ変数名」になっている。そして、初心者がそれを見てどう思うのかというと、それらの変数名が「同じでなければいけない」と思ってしまう。

だから教える人は、自分たち経験者にとっての「同じ変数名でなくてもいい場所」が、初心者にとっては「同じ変数名であってはいけない場所」なのだということを理解しながら教えてあげなければいけない。

もちろん、ある程度習熟してくれば、これらの箇所で同じ変数名を使いたくなるのは自然なことだと思う。

だけど、「同じ変数名にしてもいい(場合によってはその方が便利)」ということと、「同じ変数名にしなければならない」ということはやはりまったく別のことだ。

そして初心者にとっては、「なぜ同じ変数名にしたのか」を説明されなければ、それは「同じ変数名にしなければならない」という意味になる。

どうもこのたぐいのズレがプログラミングの入門書では散見される。そしてそのつど、「ああ、わかってないな・・」と思ってしまう。*1

変数名というのは、その中身がどんな値であるのか、外側から(中身を見なくても)大体判断できるように付ける目印というか、外装みたいなものだろう。

プログラミング経験者は、コード全体の文脈を踏まえて変数を見ているから、その中身をすぐに想像できるだろうけど、初心者はそうではない。初心者は同じ変数名を見たら、中身も同じなのだろうと思ってしまう。あるいは、「中身は違うかもしれない・・けど、確証はない・・」という状態。言い換えると、変数の中身を頭の中でトレースできていない。

中身も役割も異なるのに、同じ変数名が付いているというのは、人間で言ったら「別人なのに全身同じ服」みたいなもので、だからプログラミング初心者にとっての「値が異なるのに同じ変数名」という状況は、欧米人にとっての「背格好がほぼ同じでまったく同じ服を着ている日本人」みたいなもので、見た目から中身が異なることを判断するのは難しい。というか単純にまぎらわしい。

変数名は中身を想像させるために付けているのだから、経験者ほどにはその中身を想像できない初心者に対して、そういうまぎらわしいことをしてはいけないと思う。外から見ただけで、「ああ、あそこで使った変数をここでも使い回せるのか」と直感的にわかった方が、より効率的に、本質的な構文を学びやすくなるのではないだろうか。

話を戻して、上記の修正はメソッドの呼び出し部分にも影響を及ぼすから、一応全体に修正を行き渡らせたサンプルも示しておく。

class Menu
  attr_accessor :name
  attr_accessor :price
  
  def initialize(name_foo:, price_bar:)
    self.name = name_foo
    self.price = price_bar
  end
  
  def info
    return "#{self.name} #{self.price}"
  end
end

menu1 = Menu.new(name_foo:"すし", price_bar:1000)

puts menu1.info

https://i.gyazo.com/b61dff70f93446898a9fa85426e0e0e8.gif

しかしこの、「同じ変数名でなくてもいい部分」をつい普段のクセで「同じ変数名」にしてそのまま入門書などに書いてしまう現象、なにか名前をつけたい。そしてこうした問題がくり返されないための目印みたいなものにしたい。

*1:とはいえ、本の場合はこういうズレは編集者さんが見つけるべきだと思うけど。執筆者はプログラミングのプロではあっても、入門書のプロであるとは限らないわけだから。

文字起こししないインタビュー記事の作り方

前回、自動文字起こしについて書きましたが、

note103.hateblo.jp

今回は、文字起こしをせずにインタビュー記事を書く方法について書いてみます。

事例

これについてはすでに適用事例がありまして、もう1ヶ月以上前になりますが、以下の記事をこの方式で書きました。

geek-out.jp

普段引きこもりがちなぼくにしては珍しく、このときは山口まで出張して、YCAM(ワイカム)というアートセンターで同館のスタッフとしてあらゆるドキュメンテーション業務、また広報その他の分野において、ITを駆使しながら活躍されている渡邉朋也さんにインタビューしてきました。

*同記事については、以下のブログでも簡単に紹介しました。

note103.hatenablog.com

渡邉さんはとにかくすごいバイタリティの方で、どれだけ面白い話をしても尽きないし、ぼくもけっこうお付き合いが長いのでずーっと話していたらトータルで7時間弱、ICレコーダーが回っていまして。そうなると、もうこれ文字起こしなんかしてたら一生原稿できないな〜・・ということで、今回の方法を開発することになったのでした。

ちなみに、じゃあ普段はどうしているのかというと、たとえばぼくがこれまで手がけてきた以下のCDブックの場合、

このブックレットでは、毎回大体3万字弱ぐらいになる座談会を掲載しているのですが、これは平均で4時間ちょいぐらい、ずっと参加者の皆さんがおしゃべりをして、その録音をもとに8万字ぐらいの文字起こしテキストを作って*1、それを見ながら構成(文章全体の構造や流れなど)を立てて、その後はどんどん不要な部分をカットしながら原稿を作っていきます。

しかしながら、今回の7時間録音はさすがに文字起こしはしたくないというか、やってはいけないというか、文字起こし自体が目的(提出原稿)ならそれでもいいんですが、目的はその先にあるまとまった原稿であり、また現実的に締切りなどもあるので、「いかに効率よく、時間をかけずに、良質な記事を作れるか」という目的のもと、工程自体を新たに考えた次第でした。

作業の流れ

さて、その具体的な工程ですが、大まかに記すと以下の2つの流れに分かれます。

原稿編

  1. 事前に作った「A. 質問事項」と「B. そのカテゴリ」を原稿の「A. 文中の質問」と「B. 小見出し」に置き換えて列記する。
  2. 当日の実際の流れに応じて、原稿上の小見出しや質問を編集(追加・削除・修正)。
  3. それぞれの質問事項に対して、現場での相手のコメント(回答)をひたすら思い出しながら書いていく。
  4. 記憶に曖昧なところがあれば、キーワードマップ(後述)で該当箇所を特定し、音声を聴いて発言を厳密に把握した上で煮詰めていく。
  5. もうこれ以上できません! というぐらいまで3と4をくり返す。
  6. 文字数に合わせて分量調整。原稿を仕上げる。

キーワード起こし編

  1. とりあえず音声ファイル全体のうち、キリのいい時間で区切って(1時間ごと、または全体の2等分・4等分・8等分時点など)そのポイントだけ数秒〜数分ずつ聴いていく。
  2. 聴いた時点でどんな話をしているか、単語や短文など簡単でよいので、スプレッドシートのフォーマットに合わせてメモしていく。
  3. 1, 2の時間間隔を徐々に狭めながら、スプレッドシートを埋めて「キーワードマップ」を完成させていく。10分おきか5分おきぐらいまで詰めればひとまず充分。

後者の「キーワード起こし」というのがこの方法のキモで、これが従来の方法における「文字起こし」の代わりになります。

キーワード起こし/キーワードマップ

ということで、そのキーワード起こしについてもう少し解説します。解説用のサンプルとして、いつものように今年3月に沖縄で登壇したときの再現動画を使います。*2

www.youtube.com

これに対して何をやるのかというと、まずは録音全体を任意の間隔で区切り、それぞれの時点でどんな話をしているのか、スプレッドシートにメモしていきます。

この発表は全長85分ということで、それほどの長尺でもないので、5分間隔で区切りたいと思います。もしこれが7時間とかだったら、とりあえず15分おきぐらいまででいいと思います。あまり細かく区切るとやる気が続かないので・・。

とはいえ、85分でも初めから5分おきに聴いていくのは大変なので、まずは半分に分割してみましょう。大雑把に80分と考えて、半分の40分時点ではどんな話をしているでしょうか?

*視聴中・・

はい、発表全体の内容を知らないとちょっとわかりづらいかもですが、「再帰的にどうしたこうした」と言っています(40:20頃)。ここでは「シェル(ターミナル)」を使った作業に関して話していて、具体的には自作の「f」というコマンドを使って、任意のディレクトリ内で再帰的にファイルを検索する方法について喋っています。

それを把握できたら、以下のような感じでGoogleスプレッドシートに記入します。

f:id:note103:20180802114340p:plain

A列の10行目、「40」とある行に、

シェルのパートでfコマンドについて。再帰的にどうしたこうした

と書きました。

では、もう少し分割して、今度は20分時点と60分時点を埋めてみます。

20分時点では、「どんどんカットしていける。後で戻せる」と言ってますね。この部分は先ほどのシェルの話の前のパートで、Vimに関する話をしているようです。

60分時点では、「ついでというか。こんなのもやってますよ、というのをここに書いてますけど」と言ってます。こちらはシェルの話のひとつ後のパートで、Perlに関する話をしています。

ということで、これらを先ほどと同様に記入すると、こんな感じ。

f:id:note103:20180802114410p:plain

A列の「20」「60」と書かれた行に、それぞれ上記のメモを入れています。

こんな具合にメモを入れつつ、どんどん間隔を狭めていって、最終的にはとりあえず5分おきに「その時点でどんな話をしているのか」を記入したら作業完了です。

なお、これらの作業をするときには、上の例からもわかるように、喋ってる内容をそのまま書いてしまっても構いませんし、発言そのものには触れず、概要や感想をさらっと書くだけでもOKです。

たとえば、20分時点のメモでは、最後に「たぶんゴミ箱の話」と書いていますが、これはそのときに「Vimを使って文章を仮置きしておく場所」を「ゴミ箱」と呼んでいたことを指しています。こうしてヒントのようにキーワードをくっつけておくと、後で役立つことがあります。

ちなみに、上の方でもちらっと書きましたが、ぼくはこの作業自体を「キーワード起こし」と呼び、その結果としてできたものを「キーワードマップ」と呼んでいます。よって、この後は基本的にそのような言葉の使い分けをしながら説明していきます。

話を戻すと、このように5分単位(あるいは面倒なら10分単位でも15分単位でも)で「そのとき何を話していたか」を示すヒントというか、アンカーというか、索引を記しておくことで、後から「あの話、7時間のうちの何時間何分ぐらいの時点で喋ってたんだっけ・・」と思ったときに、効率的にその箇所を特定し、元の発言を聴き直すことができるようになります。

キーワードマップの効果と由来

ところで、上のスプレッドシートの画像を見ると、縦方向に5分おきに進む行だけでなく、横方向に0〜4まで進んでいく列も用意されています。次はこの部分について解説します。

これら横方向に伸びる列は、1分単位でメモを取りたい場合に使います。たとえば、上記シートのA3は「5」ですから、5Bには5分時点の発言にもとづく情報(メモ)が入っていますが、そのひとつ右に進んだ5Cは、5分に1分加算した6分時点の情報が入ります。同様に、5Dには7分時点の情報が入ります。

つまり、A行に記された5分おきの数字に、B〜F列の「0〜4」を足すと、そのセルの時間が算出されることになります。

「え、ということは、結局1分おきにメモを埋めなきゃいけないわけ? だったらやっぱり文字起こしぐらい大変なんじゃないの?」と思われるかもしれませんが、そうではありません。

この1分単位のマスは、あくまで「必要になったらここに書く」というために作ったものなので、必要が生じるまでは空マスのままで大丈夫です。

とはいえ、おそらく最終的には、この1分ごとのマスもけっこう活用されることになるとは思います。というのも、原稿を作成する過程においては、現場での発言内容を細かく精査していくことになるので、そうなると、次第に5分おきのメモ程度では足りなくなるんですよね。(この実例はもう少し後の方で画像で示します)

また、たとえばインタビューの中でとくに重要なキーワードが27分30秒時点で飛び出したような場合、後からそれを参照したくなることが何度もあるのですが、ここで5分おきにしか情報が入っていなかったら、対象の発言が存在している場所は「25〜30分のどこかにある」ということまでしかわからないので、それを見つけるまでに最大5分かかることになります。しかし、1分単位で記録してあれば、その後同じ情報にアクセスする際、「27〜28分のどこかにある」という風に1分以内に絞り込まれた状態で情報を取り出すことができます。

以上のように、キーワードマップとは、音声情報をテキスト情報として検索するためのツールなのだと言えます。もちろん、同様のことは文字起こしテキストでも(その方がより高い精度で)実現できるわけですが、それを作るにはあまりにも時間と体力を消費してしまうので、いわば「簡易版文字起こし」として、手っ取り早く、かつそれなりに効果を発揮するツールとして考えられたのがこの「キーワードマップ」で、それを最小限の労力で作る方法が「キーワード起こし」なのだと言えます。

ちなみに、この仕上がり状態を「マップ」と呼ぶのは、縦軸の行と横軸の列から特定のポイントを割り出していく様子が、緯度と経度から場所を特定していく「地図」のイメージに近いことによります。

時短・省力化

この手法を「労力」や「作業時間」の観点から考えてみると、最初の5分おきにメモを埋めるところまでなら1〜2時間もあれば充分でしょうし、また1分おきのメモにしても、必要に応じてそのつど数分で埋めていけるので、すべての作業時間を合わせても1日分の作業量にもならないと思います。

*ちなみに、1分ごとにセルを埋めていくときには、文字起こし的に、喋っている内容をそのまま入れてしまった方がラクだと思います。いちいちメタ的視点から感想や意見を考えていると、そのたびに頭を使って疲れてしまうので。逆に、音声をそのまま文字化していくのであれば、あまり頭を使いません。この一連の方法は、時短・省力化を目的のひとつとしていますから、無理をして疲れてしまっては意味がありません。

一方、いわゆる文字起こしというのは非常に時間がかかるもので、ぼく自身の経験で言うと、完成するまでには元音声の12倍の時間がかかるので、今回の音声なら85分*12/60で、17時間かかる計算になります。また、文字起こしは大変体力を使うので、1日4〜5時間に留めるとして*3、まる4日は使うことになります。

その上、冒頭に挙げた記事であれば音声ファイルは7時間弱という超・長尺ですから、これを文字起こしするには通常の何倍もの時間がかかる上、できあがるテキストも膨大な量になり、それに対する編集作業の労力は加速度的に膨らみ、さらには使われないテキストも多くなるため、ようは元の音声が長いほど文字起こしの非効率さは増大していきます。

ぼくの場合、普段のこういった原稿作成では、まず録音から文字起こしテキストを作成し、それを読みながら構成を練ったり、どの部分を残してどの部分をカットするかと考えたりするのですが、上記のような理由により、今回はその「文字起こしをもとに原稿を作っていく」のではなく、「まず完成像をイメージしながら構成を組み立てて、そのディティールを埋めていく際にキーワード起こしを(文字起こしの代替として)使用する」という方法をとることにしました。

木彫か粘土か

ここで一瞬、工程の説明から離れて概念的な話になりますが、上記の「文字起こしから作るのではなく、構成を立ててからキーワード起こしを使う」というイメージは、前者を木彫、後者を粘土制作に置き換えて捉えることができます。

前者の作業では、まず膨大な文字起こしテキストがあって、その中から不要な部分をカットしながら、本当に必要なところだけを残していきます(厳密には、カットした部分にも大事な要素はたくさんあって、それが残った部分に投影されるような操作をしていますが、大局的には大差ないのでここの説明では無視します)。

ぼくはこの作業をイメージするときに、丸太から仏像を彫り出す作業を思い浮かべます。仏像を彫る人は丸太を見ているようで、じつは丸太の中に初めから存在している仏様を抜き出すようにして、仏様以外の部分を削り取っていきます。*4

一方の後者、粘土制作の方は、初めに大きな粘土のカタマリがあるわけではなくて、まずは木片や針金などを使って芯棒を組み立てて、そこに徐々に粘土をくっつけていきます。*5

そして今回採用した方法は、普段やっている前者の木彫スタイルではなく、後者の粘土スタイルであったと言うことができます。

原稿作成の手順

では話を戻して、ここからは上記のキーワードマップを使って、どうやって原稿を作っていったのかを解説していきます。

1. 事前に用意した質問を並べて原稿の叩き台にする

前記の手順から「原稿編」の方を再掲すると、

  1. 事前に作った「A. 質問事項」と「B. そのカテゴリ」を原稿の「A. 文中の質問」と「B. 小見出し」に置き換えて列記する。
  2. 当日の実際の流れに応じて、原稿上の小見出しや質問を編集(追加・削除・修正)。
  3. それぞれの質問事項に対して、現場での相手のコメント(回答)をひたすら思い出しながら書いていく。
  4. 記憶に曖昧なところがあれば、キーワードマップ(後述)で該当箇所を特定し、音声を聴いて発言を厳密に把握した上で煮詰めていく。
  5. もうこれ以上できません! というぐらいまで3と4をくり返す。
  6. 文字数に合わせて分量調整。原稿を仕上げる。

ということで、まず1番の質問事項について、これはインタビューの現場で「とりあえずこれについては聴いておきたい」という内容を事前に書き出しておいたもので、ぼくの場合はルーズリーフ2〜3枚に手書きでリスト化して、色ペンでさらにマーキングやメモなどを付して用意していました。

このときの質問は全部で23個でしたが、質問にはある程度共通する傾向というか、カテゴリが存在するので、そのカテゴリごとにまとめておきます。

冒頭の記事で言うと、最終的には以下5本の見出しが立っていますが、

  • YCAMってなに?
  • YCAMインターラボの活動
  • IT技術の生かし方
  • オープン化の取り組み
  • 既存の文化事業を超えて

元々の質問はプラス2〜3本(計7〜8本)のカテゴリに分かれていました。

で、この段階では実際のインタビュー内容については一旦忘れて、その質問カテゴリを「小見出し」として、またそれぞれにぶら下がっている質問事項を各見出し内のコンテンツとして列記していきます。それだけ、といえばそれだけですが、これがこの後の作業の大元というか、叩き台になります。

2. 質問の内容やカテゴリ(小見出し)を実態に即して調整

1の状態はあくまで事前に作った資料を元にしたもの(叩き台)なので、今度はこれを現場で行われたインタビュー内容に合わせて調整していきます。

ただし、ここでは次の工程3と同様、当日の流れを厳密に再現する必要はありません。事前に予定していた質問のうち、カットしたものを外したり、追加したものを適宜加えておく程度で充分です。

3. 想像力で回答を埋めていく

次に、上記2で作った質問に対する相手の回答を、自分の記憶や想像でどんどん埋めながら全体の流れを作っていきます。このとき、相手が実際にそう言っていたかどうか、という確認はひとまず後回しで大丈夫です。

なぜなら、第一に大半の内容はその後に修正することになるからで、第二に、この段階でそういった確認をちまちまやっていると全体の流れを作りづらくなるからです。通常、読者は記事を最初から最後まで1本の線を辿るように読んでいきますが、現場ではあちこちに話が飛びながら進んでいますから、現場の実態を重視していたらいつまでも読者に読みやすいものは作れません。

よって、ここではあたかも自分が最初の読者であるかのように、「こういう流れでこの記事を読みたい」と思う文章を作っていきます。上記1と2の工程はその準備であり、この3はそれを踏まえた通し稽古のようなものです。実際の進行順と異なるからといって、「事実と違うぞ!」などと指摘してくる人はいませんし、そもそも執筆者は現場で話した内容を誰よりも把握しているはずなので、「事実と異なるかも」などということは心配せず、まずは自分の想像の世界で1本の物語を作ってしまいましょう。

4. 記憶が曖昧な箇所をキーワードマップで確認する

ある程度、質問と回答のやり取りを埋めて全体の流れができたら(あるいはその途中でどうしても現場の発言を確認したくなったら)、ここでキーワードマップの登場です。おもむろにGoogleスプレッドシートの検索機能を使って、検索したい箇所で話しているはずのキーワードを入力し、音声ファイルの中のどこでそれを話していたのか、突き止めましょう。

不思議なことに(あるいは当然のことかもしれませんが)、現場で話したことや、相手から聞いた言葉というのはけっこう頭の中に具体的に残っているもので、それを検索クエリとして使うと、大抵の発言は見つけ出すことができます。

たとえば、山口の記事で使ったキーワードマップで「パブリシスト」を検索すると、こんな感じになります。(一部伏字の上抜粋)

f:id:note103:20180802164652p:plain

はい。1時間21分の時点で話題に上がっているようです。

もし目当てのキーワードが見つからなかったら、その前後の記憶を辿って、近いところで話しているはずの別のキーワードを検索してみましょう。それでも見つからなければ、最初にマップを作った際の間隔が少し広すぎる可能性があるので(10分、15分間隔になっているなど)、一旦作業を止めて、5分おきぐらいまで詰めておくのも有効です。

大抵の場合、5分おきまで詰めておけば、ピッタリその箇所を見つけられなかったとしても、「この20〜35分の範囲で話題に挙がっていたことは間違いない」ぐらいまでは特定できるので、そこで15分間、再生速度を上げて音声を聴き通してもよいかもしれません。

5. 通して読める原稿に仕上げる(分量は調整不要)

あとはひたすら、3と4をくり返します。このとき、文字量についてはまだ考えなくて大丈夫です。まずは自分が通して読んで面白いと思えるまで原稿を仕上げてしまいましょう。

また、3の段階では記憶だけで流れや内容を作っていきましたが、この段階で、すべての発言内容に関して、「相手が本当にそのような発言をしていたかどうか」を確認します。

といっても、ここでは本人の「現場での発言」と「文中の発言」が一言一句同じである必要はありません。記憶で再現した発言と実際のそれが完全に一致するはずはありませんし、一致させなければならない理由もありません。むしろ、一言一句同じにしたところで本人の言いたいことを再現できるとは限りませんし、逆に記憶や想像をもとに再現した文章の方が、より自然に本人の言いたいことを伝えられている場合もあります。

文字起こしをもとに文章を作る際には、つい現場での実際の発言に引っ張られがちになりますが、この方法を使えば、そうした足かせにとらわれることなく、より相手の意向を汲み取りながら、臨場感のある文章を作りやすくなります。

そしてこのとき、キーワード起こしは相手が本当にそういう意味のことを言っていたことを確認するための裏付けとして、あるいは「相手が言ってもいないことを言ったことにしていないか」を確認するためのチェックシートとして機能します。

なお、今回の山口の記事では、この段階で冒頭のリード文(インタビューに入る前の簡単な解説)と、最後のプロフィールも作っておきました。

導入と締めを置くことで、よりリアリティを持って、「最初の読者」として本文を読み通しやすくなります。

6. 原稿の仕上げ。分量調整

最後に、原稿の分量を目的の文字数まで調整します。なお、これまでとくに説明していませんでしたが、この段階の原稿の文字数は、目的の文字数よりもオーバーしている前提です。もし目的よりも少ない場合には、手順2まで戻って構成を再考する必要があるかもしれません。

一方、今回の方法では上述の「木彫」方式ではなく、「粘土制作」方式ですから、目的の文字数に比べてべらぼうにオーバーしていることもないはずです。ある程度の構成・構造を立てた上で、その完成像をイメージしながら肉付けしてきたので、着地点も比較的目的に近いところにあるのではないかと思います。

その他、原稿の仕上げに際して、執筆者として気をつけることはいろいろあると思いますが(読者に対して提示すべき前提や、用語の説明に抜けはないかなど)、今回の本論とはズレるので割愛します。*6

まとめ

以上、「文字起こし」を使わずに「キーワード起こし」でインタビュー記事を作る方法をまとめてみました。

いつものように、だいぶ長くなってしまいましたが、要点は、

  • 録音した音声が長大になるほど、文字起こしを使った方法では効率が悪くなる
  • キーワード起こしを使えば、その無駄を軽減できる。(時短・省力化)
  • キーワード起こしを用いる場合には、木彫型ではなく粘土型にスタイルを切り替える

といったところでしょうか。

個人的には、この方法を使うことによって、執筆者は現場での発言に縛られすぎることなく、より自由で本質的な表現を実現しやすくなると思っています。

ただし、それは「キーワード起こしの方が文字起こしより優れている」という意味ではありません。

たとえば、1時間以内ぐらいの内容だったら、一気に文字起こしをしてしまった方が早いかもしれませんし、あるいはインタビューではなく、座談会のように複数の人が同時に喋るようなものだったら、記憶しておける現場の情報も限られてしまい、文字起こしに頼った方がよいかもしれません。また、事前の構成ができないような場合も、素直に文字起こしから入った方がよいかもしれません。

具体例としては、初めの方で触れた、坂本龍一さんの音楽全集に掲載している座談会にしても、事前にわかっているのは大まかなテーマとそれにもとづくいくつかの主要曲ぐらいで、あとはその場の流れに合わせてほとんど即興で話が進んでいきます。よって、この場合は事前に構成を組んでから肉付けしていくような作り方は不可能で、まずは文字起こしを中心にベースの原稿を作って、それを眺めながら、自分の意識は極力排して、テキストが進みたい方向へ進むようにサポートしてあげる、という感じで手をかけていきます。*7

ただいずれにしても、手法の選択肢が増えれば、それだけ作業の効率が上がり、成果物のクオリティが上がる可能性も高まるので、誰かの参考になればと思ってまとめてみました。

*1:この音楽全集の文字起こしは直近でも昨年の1〜2月頃にやったものなので、当時はまだGCPの使い方なども知らなかったし、Googleドキュメントでの文字起こしもそれほど費用対効果が高くなかったので今のところすべて人力で起こしてます。

*2:誰にも許可を取ったり気を遣ったりする必要がなく使いやすいので。

*3:実際にはそんなにやらない気もしますが・・。

*4:あくまで喩えなので、本当にそんなことを考えてやっているかはわかりません。

*5:これも喩えなので、実際のところはわかりません

*6:文字校正などについても同様の理由で割愛。

*7:オカルトっぽい説明ですが、きちんと説明しはじめるとそれだけでまた長文になるので割愛。

21世紀の文字起こし(3) 〜 Cloud Speech-to-Text 編 〜

ここまでのあらすじ

少なからぬ人々が直面する文字起こし(音声を文字に変換する作業)について、手動でパチパチやっていくのはけっこうつらいものがあるので、なんとか自動化できないか? というこのシリーズ。

気がつけば最初の記事はちょうど2年前の今頃に書いていて、続編はその半年後。で、それからさらに1年半ぐらい経ったわけですが。

note103.hateblo.jp
note103.hateblo.jp

最初の記事は、たしかiPhoneの音声入力機能を使って、耳から当該音声ファイルを聴きながら自分でそれを追いかけて喋るという、いわばシャドウイング方式でやったらこれ文字起こしになるじゃん! という素朴な発見から始まって。

そのまま連想的に「でも自分で喋らなくても、SoundflowerとGoogleドキュメントの音声入力を組み合わせればなんか自動でできそうじゃね?」と思ってやってみたら「うわ、できるじゃん(笑)」みたいな流れだったかなと。

続編の方は、その最初の記事では憶測で書いていたような各種の条件的な部分をもう少し厳密に検証して、「何ができて、何ができないのか」を自分なりにまとめたものでした。

で、それから2年が経って今はどうかというと、Googleさんの技術の進歩は目覚ましく、最初の記事では「1分もしないうちに止まってしまう」とか言っていたのがほとんど永遠に起こし続けるようになり、内容の精度もだいぶ上がっているように感じられます。

しかしその一方で、じつはGoogleさんはそれとは別の音声認識サービス「Cloud Speech-to-Text(旧称・Cloud Speech API)」も開発・提供していて、とくにここ半年ぐらいはそれを使ったり試したりする人を目にする機会が増えました。

cloud.google.com
cloudplatform-jp.googleblog.com

そして、これは同じGoogleさんのサービスでありながら、上記のGoogleドキュメントを使った音声入力(を使ったハック)と異なる点が少なくありません。

ということで、今回はそのCloud Speech-to-Textを使って文字起こしをする方法と、それを通して出力された結果がどんな感じなのか、記していきたいと思います。

免責事項

本題に入る前に明記しておきますと、ぼくは普段プログラミングやITに関わる仕事をしているわけではなく、以下の内容はどれも100%趣味のレベルで調べたり試したりしたことですので、間違いを含んでいる可能性もありますし、下記の情報を元にトライした誰かがそれによってなんらかの損害を被ったとしても、ぼくがその責任を負うことはできません。その前提でご参照ください。

Cloud Speech-to-Text の使い方

参考資料

通常、参考資料というのはこういった記事の最後に列記するのが大半だと思いますが、この後に紹介する文字起こしの方法は、そのほとんどがこれら参考資料の刷り直しみたいなものにすぎないので、各記事への敬意と感謝を込めて最初に示しておきます。

投稿された時系列に示しますと、以下の記事群がとても参考になりました。

  1. Google Cloud Speech APIで音声の自動文字起こし
  2. Google Cloud Speech API を使った音声の文字起こし手順
  3. 超初心者でもgoogle-cloud-speechを使えるが、つまずいた所はある。
  4. 【GCP】超超初心者がGoogle speech api を使うところ。(wav形式)

最初の記事では、この手法全体の流れやエッセンスがコンパクトにまとまっています。特徴的なのは、最後に「どういう音声が認識されやすいのか」をまとめてくれているところで、なるほど感がありました。(これについては終盤であらためて言及します)

2番めの記事は手順が非常に詳しく書かれていて、個人的に一番参考になったのはこれだと思います。とくに、認証まわりはけっこうコケやすいというか、めげやすい部分だったのですが、同記事を何度も読み返しながらなんとかクリアできました。

また、最初の記事と2番めの記事には、この後に出てくるPythonのコード例が示されていてとても助かりました。両者のコードは1〜2行を除いてほとんど同じなので、後者が前者を参考にしたのか、あるいは両者が同じ元ネタを参考にしたのかわからないですが、いずれにしてもやりたいことはできたので感謝しています。

※そのコードの元ネタについて、検索し回ったかぎりでは公式ドキュメントのサンプル群から辿りついた以下が一番近そうとは思いましたが、ちゃんとは検証していません。
python-docs-samples/transcribe_async.py at master · GoogleCloudPlatform/python-docs-samples · GitHub

話を戻して、上記3番目の記事はエラー集。じつはこのサービス、一度うまく行った後にもいくつかハマりどころが待っているのですが、そういう「理解したと思ってたのになぜかコケてる・・」みたいなときにこの記事がとても役立ちました。

最後の記事では、WAV形式の音声ファイルを使う場合の方法が説明されています。ネット上の解説記事ではFLACというファイル形式を前提にしたものが大半なので、どうしてもWAVを使いたいという場合にはそちらを参考にするとよいと思います。

音声ファイルを作る

では、ここから具体的な作業について記していきます。

最初に作るのは、今回文字起こしをする音声ファイルです。すぐ上にも書いたとおり、このサービスではmp3やm4aなどではなく、FLACという形式の音声ファイルを使いたいのですが、一般的には、音源をFLAC形式で管理したりはしてないですよね・・少なくともぼくはしてないです。

ということで、ここではmp3などのFLAC以外のファイル形式からFLACに変換し、またその他のいくつか必要な変換操作も一緒にやっておきます。

まず変換前の音声ファイルですが、今回は今年の3月にYAPC::Okinawaで登壇したときの内容の再現動画(Link)から、終盤の数分だけをmp3形式で抜き出しておきました。

soundcloud.com

次に、このファイルを変換するために、無料の音声編集ソフトウェアであるAudacityを立ち上げます。

Audacityについては以下の記事で利用方法等を解説していますので、よろしければどうぞ。

note103.hateblo.jp

Audacityを使った操作は、以下の3つです。

  1. サンプリングレートを44,100から16,000にする。
  2. ステレオならモノラルにする。
  3. FLACファイルとして書き出す。
サンプリングレートの変更

まず、サンプリングレートを変えます。と言ってもこれは一瞬で、左下のプルダウンで切り替えるだけです。

ここから・・
f:id:note103:20180722123132p:plain

こんな感じで。
f:id:note103:20180722123143p:plain

ステレオをモノラルに

次に、ステレオをモノラルにします。上記の画像を見ると、ひとつのウィンドウに2本の波が並行して走っていますが、これがステレオであることを示しています。

逆に言うと、最初からこれが1本だけならこの作業は不要(すでにモノラル)です。

ではやってみましょう。ステレオ音声をモノラルにするには、まず当該音声の枠内を適当にクリックして、
f:id:note103:20180722124546p:plain

そうすると上部メニューバーの「トラック」にある「ステレオからモノラルへ」という選択肢がアクティブになるので、そいつをクリック。
f:id:note103:20180722124557p:plain

すると・・モノラルになります。
f:id:note103:20180722124427p:plain

FLAC形式に変換

最後にFLAC化。これはメニューバーの「ファイル」から「オーディオの書き出し」を選択して、*1

f:id:note103:20180722131327p:plain

「ファイルタイプ」をプルダウンからFLACにして・・

f:id:note103:20180722130603p:plain

右下のボタンで保存。ちなみに、自分ではとくに触っていませんが、フォーマットオプション欄の「量子化ビット数」が16bitになってますね。上記のQiita記事(2番め)によると、これも要件のひとつのようです。

f:id:note103:20180723193626p:plain

すかさずメタデータを記入する画面が出てきますが、ここでは気にせずOK。

f:id:note103:20180722130633p:plain

すると、FLACファイルの出来上がり。

f:id:note103:20180722130651p:plain

Google Cloud Platformにアカウント登録

音源ができたので、次はテキストへの変換作業をするための作業場を用意します。

舞台となるのは、Google Cloud Platform(以下「GCP」)というGoogleさんのサービスです。その中に、今回使用するCloud Speech APIという道具が置いてあり、これを有料で使うことができます。

有料と言っても、GCPに登録した段階で3万円分のクレジット(クーポンみたいなもの)をもらえるので、それを使い切るまでは無料です。(ただし、たしか使い切る前に1年過ぎたら無効になったと思います。記憶だけで書いていますが)

GCPの利用を開始するには、Googleアカウントとクレジットカードの情報が必要です。これは主に身元確認のために必要だそうで、その辺も含めて、登録まわりの方法については、ぼくはすべて以下の書籍を見ながらクリアしていきました。

同書はプログラミングの初心者・・よりはちょっと知識のある、しかしおそらくIT企業の新入社員(または学生)ぐらいの技術レベルに合わせて書かれたような感じで、ちょうどぼくのレベルにフィットするわかりやすい本でした。

今回の記事ではそこまで踏み込んで解説しないので(それもやったら1ヶ月ぐらいの連載になりそう)、詳しいアカウント作成方法については同書やネット検索で解消してください。

新規プロジェクトを作成

アカウントを作ったら、新規のプロジェクトを作ります。

ちなみに、ぼくも普段使っているプロジェクトをここでそのまま紹介するのはちょっと気をつかうので、この記事のために新たなプロジェクトを作ります。

まずはダッシュボードに行って、検索窓で「リソースの管理」を探します。*2

f:id:note103:20180722135005p:plain

「リソースの管理」ページに行ったら、「+プロジェクトを作成」をクリック。

f:id:note103:20180722134206p:plain

プロジェクト名を入れます。

f:id:note103:20180723112830p:plain

ここで一点注意ですが、「プロジェクト名」を入力すると、それに応じた「プロジェクトID」も生成されます。しかし、その「プロジェクトID」は世界で1つだけの(一意の)文字列である必要があり、つまり「MyProject」みたいなコテコテのIDは今さら取れるはずもなく、そのようにすでにIDとして取得されている文字列を「プロジェクト名」として入力した場合には、自動的に「MyProject-192876」みたいなID候補が生成されて、「プロジェクトIDはこれでいいよな? 」みたいに言われます。

「プロジェクト名」の方は一意である必要はなく、なんでも好きなもので良いので、もしIDが「MyProject-192876」とかでも良ければ、気にせずプロジェクト名を「MyProject」にしておけばいいのですが、個人的にはこの「プロジェクト名」と「プロジェクトID」がズレると結構混乱するので(とくに使い始めの頃は)、ぼくの場合はプロジェクト名を付ける段階でグローバルに一意の文字列になるようにしています。

ということで、今回は「note103-speech-sample」としました。左下の作成ボタンを押すと・・

f:id:note103:20180722134236p:plain

はい、できました。当該プロジェクトをクリックして、その中に入りましょう。

(ちなみに、ひとつ上にあるプロジェクト「note103-speech」は普段使っているものなので無視してください)

音声ファイルをアップロードする

では次。先ほど作ったFLACファイルをGCPにアップします。具体的には、GCPクラウドストレージに専用バケットを作って、その中にアップロードしていきます。

まずは検索窓に「storage」と入れて・・Cloud Storageが見つかるので、そこに飛びます。

f:id:note103:20180722182745p:plain

バケットを作成」をクリック。

f:id:note103:20180722182811p:plain

バケット名を登録します。これも先ほどのプロジェクトIDと同様、一意にする必要があるようです。(今のところカブる名前を試してないので、憶測ですが)

ここではバケット名を「note103-bucket-sample」として、ストレージクラスは「Multi-Regional」、場所は「アジア」にします。(ストレージクラスと場所の設定は上記のGCP本で使用していた設定に合わせています)

f:id:note103:20180722182837p:plain

作成と同時にバケットの中に移動するので、アップロードボタンから先ほどのFLACファイルをアップします。

f:id:note103:20180722182912p:plain

速攻で完了しました。バケット内に対象のファイルが入っています。

f:id:note103:20180722182943p:plain

APIの有効化 & サービスアカウントキーの作成

次に、本サービスで利用するCloud Speech APIの有効化と、サービスアカウントキーの作成という作業を行います。が、これについては前掲のQiita記事に詳しい説明があるので、そちらをご参照ください。

qiita.com

ぼくもそれを見てやったのと、それ以上にうまく説明できるわけでもなさそうなのと、なにより同じことをここでくり返しても人類の損失なので・・。*3

なお、サービスアカウントキーを作る際の「役割」という項目について、そちらの記事ではとくに言及されていませんが、未設定のままだと怒られたので、ぼくの方では「Project/オーナー」としました。

Cloud Shell にJSONファイルをアップロード

上記の手続きどおりにサービスアカウントキーを作成すると、自動的にJSONファイルがローカルにダウンロードされます。

今度は、そのJSONファイルをCloud ShellというGCP上の作業環境にアップロードします。面倒な作業が続いていますが、そろそろ佳境というか、案外ゴールはもうすぐです。

Cloud Shellは、GCPの画面のどこにいても大抵開けるようです。たとえば、ダッシュボードからだとこの辺にボタンがあるので・・

f:id:note103:20180722183035p:plain

クリックすると、以下のように画面の下半分にシェル(ターミナル)が出てきます。で、このままここで作業してもいいのですが、個人的にはちょっと画面が狭くて、見通しの悪さが疲れを誘発するので、別画面に展開してしまいます。その場合は、右上のポップアップボタンをクリックします。

f:id:note103:20180722183058p:plain

展開できました。広々〜。

f:id:note103:20180722183208p:plain

ちなみに、このシェル環境はプロジェクトごとではなく、アカウントごとに割り当てられているようです。つまり、複数プロジェクトをまたいで、同じシェル(というかOSというか)を使えるわけですね。

これは非常にありがたいことで、たとえば、今回は踏み込まないですが、.bashrcや.vimrcのような設定環境を複数プロジェクトで共通して使えるということです。

そういう環境の違いというのはじわじわと作業効率に影響を及ぼすもので、たとえば素のVimを使わざるをえなかったり、それがイヤで毎回プロジェクトを作るたびに.vimrcをアップしたりといった手間がなくなるだけでもとても助かります。

話を戻して、次は先ほどのJSONファイルをアップロードします。といってもこれはポチポチクリックしていくだけで、とくにコマンドを打ち込んだりする必要はありません。

まずシェルの右上にあるプルダウンボタンから、「ファイルをアップロード」を選択。

f:id:note103:20180722183240p:plain

先ほどダウンロードしたJSONファイルを選択。「開く」で、アップされます。

f:id:note103:20180722183348p:plain

そのまま、環境変数GOOGLE_APPLICATION_CREDENTIALS」にJSONファイルのデータを設定します。

$ export GOOGLE_APPLICATION_CREDENTIALS=[ダウンロードしたJSONファイル].json

認証まわりについては以上です。

Pythonファイルの準備

では、いよいよ準備の大詰め、命令を実行するためのPythonのコード&環境を用意します。

まず、今回使用するPythonのコードですが、冒頭で紹介したQiita記事に掲載されていたものをほぼそのまま使います。ぼくは出力用のファイル名に関するところだけ微調整して、具体的には以下のようにしています。(ファイル名は「transcribe.py」)

gist.github.com

これを先ほどのJSONと同様の方法でアップロードしてもいいですし、あるいはShell上でVimなどを開いて直接コピペしてもよいと思います。

ちなみに、19行目にコメントアウト行が入っていますが、その行末にある「LINEAR16」というのがWAVファイルを使うときの記述です。もし対象の音声ファイルをWAVにしたい場合には、FLACを指定した18行目をコメントアウトして、19行目の方を使います。

さて、このPythonコードをホームディレクトリに置いて ls を叩くと、こんな感じになります。

f:id:note103:20180722183405p:plain

先ほどのJSONファイル(左端)と、今アップしたPythonコード(右端)がどちらも入っています。

(ちなみに、ここに見えるtmpというディレクトリは普段ぼくがCloud Shell上で使っているデータ類をこの解説のために一旦仮置きしている場所です。今回の作業とは関係ありませんので見なかったことにしてください)

最後に、Pythonを実行するためのライブラリをインストールします。

$ sudo pip install google-cloud-speech

はい。ということで、長い道のりでしたが、これでひとわたりの準備は完了です。いよいよ実行に移ります。

実行

実行の際には、以下のコマンドをCloud Shellに打ち込みます。

$ python transcribe.py gs://note103-bucket-sample/Sample-for-transcript-Cloud-Speech-API.flac

「gs://」以右の部分は、先ほどFLACファイルをアップしたバケット内の場所を示しています。

ちなみに、今回はわかりやすさを優先して音声ファイルの名前を長〜くしていますが、普段はもっと短いファイル名にして、このコマンドも短く済むようにしています。(「sample-01.flac」とか)

では、動かしてみましょう。処理時間がわかりやすいように、動画で撮っておきました。(大半は止まったままですが)

www.youtube.com

はい。大体50秒弱ぐらいで終わってますね。元の音声が2分45秒ですから、3分の1ぐらいの時間で完了していることになります。ちなみに、これは元の音声ファイルが長くなってもほぼ同じぐらいの割合で、60分ぐらいの音声なら20分程度で処理が終わります。(2018年7月時点)

では、仕上がったテキストファイルをダウンロードしましょう。今回のPythonコードでは、書き出したファイル名の頭に「output」というキーワードが付くようにしてあります。(上記のGistで30行目)

また、Cloud Shellからファイルをダウンロードするにはdlコマンドを使用するので、以下のようにすれば必要なファイルをローカルに落とすことができます。*4

$ dl output*

すると、このようなダイアログが出てくるので、「ダウンロード」をクリック。

f:id:note103:20180722203002p:plain

これで、ファイルが手元にダウンロードされます。

結果と講評

では、お待ちかねの出力結果です。この処理はぼくも今回のために初めて動かしたので、どうなっているのかまったく想像がついていません。ドキドキ・・

以下、出力結果をそのままコピペします。改行位置などもそのままです。

ということで前最後のスライドはんですけどもも簡単にまとめますとですねもう色んな活用例をですねご紹介してきたんですけどもないことがあったのかなということちょっと最後にお出しします一つはですねそのまま年表音かあるいはそのエクセルのね作業力で6列いつもより下の階2列ですお金はそういう風に単純作業は行ったとも自分がやんなくてもいいよねっていうことはその機会に行ってもらうみたいな音ことでしょうから単純作業は減るってのはそうとよく言われることだと思うんですけど個人的にはの本質的だなと思っての実はこの2番手ですね自分専用の仕事道具を作る仕事の道具ってのはその専門的になればなるほどそうな自分だけのローカルな道具みたいのが必要になったりあるいはそれがあることによって効率が合体すると思うんですけど
そういうのですねあのプログラミングをすることによってその自分でこうカスタマイズして行けるって言うかその汎用品じゃない山用品誰でも使えるものっていうのはちょっと薄く広くと言うかスネ浅く広くと言うかは誰でも使えるだけに今そこそこしか便利じゃないんだけど自分だけに便利でも他の人は何か便利かどうかも分からんみたいなやつはめちゃくちゃ高効率が上がるみたいなことがあるんでその自分に合わせた道具を作っていくってことがですねできる仲間さっきのとこそそそねえっと IDE の環境そうですけど今ちょっと一瞬またでもに戻りますけどこれとかね
こういうのはその僕が濃いの欲しいなと思って作ってるわけでも誰かが作ってくれたってよりは誰かが作ったのかを組み合わせて声のやってるわけですね
だから他の人にこれが便利かわかんないしもっと便利なのがあるかもしんないですけども僕だけに便利ぐらいのものが作ることによっては非常に効率が上がってるということでその自分のための道具を自分で作るっていうことができるのがま相当いいところなんじゃないかなということですね今これを踏む発表できたのは良かったんじゃないかなと思ってますけど最後にですねまあその楽しみが増えたら楽しくなりたくてもやったことなんでまぁ結果的にそれも多少は多少と言うかかなりという花ですね実現できたかなと思っております

はい。どうでしょうか。マルやテンがないので読みづらいですが、それでも所々に改行が入っているので、一切改行がなかったGoogleドキュメントのときよりは読みやすくなっています。

ちなみに、この改行はどういう法則で生じているのかというと、2分45秒の音声ファイルのうち、ちょうど1分と2分あたり、なおかつ話がふっと途切れたところでちゃんと改行されてるんですね〜。いや、これはすごい。

Googleドキュメントの場合は、ただひたすら1文になってダラダラ起こされていくだけなので、これだけでもかなり使いやすい(編集しやすい)テキストになっていると思います。

それから、上でも少し書いたとおり、この処理にかかった時間は元の音声ファイルの時間の約3分の1です。これについても、Googleドキュメントの音声入力を使った場合はまったく同時間が必要ですから(といってもリアルタイムに入力する前提の機能なので当たり前&そもそも比べる性質のことではないかもしれないですが)、その意味でもより便利になっていると感じます。

さらに、これも非常に重要な点ですが、Googleドキュメントで音声ファイルから文字起こしをする場合は、稼働中に別のアプリケーションを使おうとすると入力が止まってしまいます。

しかし、このCloud Speech-to-Textでは、処理が終わるのを待っている間に別のアプリケーションでどんな作業をしていてもまったく問題ありません。この「別の作業をしながら並行して自動文字起こしができる(勝手にやってくれる)」というのは、考えてみるとぼくが最初にGoogleドキュメントとSoundflowerを組み合わせたときからずっと夢見ていた状況で、もしかするとその夢、ほとんどかなってるのでは・・? と思うほど素晴らしい状況です。

テキストの精度に関しては、こちらをお読みの方それぞれの評価があると思いますが、個人的には「ふえー、ここまで出来るようになったのか、すごいな・・」というのが素朴な感想です。

もちろん、このままではまったく仕上がりとは言えませんし、ここから直していくのもそれなりに大変だとは思いますが、とはいえ、2年前に初めてGoogleドキュメントでこれを試したときに比べたら格段に良くなっていると思いますし、今後もここから悪くなることはないでしょうから、基本的には好印象を持っています。

ハマりどころ

本記事は一応、ある程度全体のフローを理解してからまとめたものですから、それなりにスムーズに、かつ望ましい結果が出ていますが、実際に試しているときはそれはもうエラーの連続でした。

エラーへの対処に関しては、初めの方に参考文献として挙げた記事群がほぼ網羅してくれていますが、個人的にポイントになりそうなところを簡単にまとめておきます。

といっても、大半は次の1点に集約されると思うのですが、それはCloud Shellのセッションが一度切れると、上記で設定した環境変数や、インストールしたライブラリがリセットされてしまうということです。

通常、ローカルの環境でそんなことは起きませんから、当然、一度設定した環境変数やインストールしたライブラリはそのまま維持されるという前提で作業をするわけですが、Cloud Shellはそうではないので、日をおいてログインしたときや、同日中でもしばらく時間が空いてセッションが切れた後だったりすると(たしか1時間未使用とか)、「さっき上手くいったはずのコマンド」がもう効かなくなって、エラーが出てしまいます。

よって、そういうときには落ち着いて、上記の

$ export GOOGLE_APPLICATION_CREDENTIALS=[ダウンロードしたJSONファイル].json

や、

$ sudo pip install google-cloud-speech

を再入力してあげましょう。大抵はそれで直ると思います。

料金

さて、初めの方にも書いたとおり、このツールは基本的に有料です。ただし、アカウント登録時にもらえるクレジットとはべつに、毎月1時間分までは無料です。よって、それほど大量にやるわけではない、という人は無料のままでも長期的に使えるかもしれません。

とはいえ個人的には、現時点での料金もけっして高いものとは思いません。具体的な価格については以下で説明されていますが、

https://cloud.google.com/speech-to-text/pricing

機能 0~60 分 60 分超、100 万分まで
音声認識(動画を除くすべてのモデル) 無料 $0.006 米ドル / 15 秒

前述のとおり60分までは無料で、そこから先は最大100万分まで「$0.006 米ドル / 15 秒」とのこと。1ドル112円として、1分単位で換算すると、「0.024米ドル*112円=2.688円」ということで、1分につき2.688円、1時間なら162円、2時間で323円ほどです。

これがしかも、60分のファイルなら20分の処理時間でこのレベルまで起こされるわけですから、手動で膨大な時間と体力を注ぎながら必死に起こしていくことに比べれば、やはりお手頃かな〜・・と。

録音時の注意点(より正確に起こすために)

一連の手順についてはひとまず以上ですが、ここで一点、大事な知見を共有したいと思います。

上記のサンプルはそこそこ綺麗に起こすことができましたが(と個人的には思っていますが)、音声ファイルによっては、まったく駄目な場合もあります。

そして、その違いが生じる一番の原因は、おそらく録音時に吹き込み用のマイクを使っているかどうかだと思っています。

ここで言う「吹き込み用のマイク」とは、べつに何万円もするような本格的なものである必要はなく、たとえばぼくは3千円ぐらいのヘッドセット(ヘッドホンにマイクが付いてるやつ)を使って録音していますが、そういうのでも充分です。(上のサンプル音声もそれで録りました)

もちろん、仕事で使うようなスタンド付きのマイクでも良いでしょうし、ハンドマイクやピンマイクから採った音でも大丈夫だと思います。

これらに共通するのは、「マイクが口元から音を拾う」ということで、逆に言うと、たとえばICレコーダーをテーブルに置きっぱなしにして録音したインタビューとか、ビデオカメラに付属しているマイクで録った音声とか、そういう「周りの音ごと全部拾った音声」だと、綺麗にテキスト化されないケースが多いと感じます。

この音質については、冒頭に挙げた参考記事のうち、以下でも言及がありました。

qiita.com

曰く、

精度に関係しないもの

  • マイクの感度
  • 話すスピード
  • ノイズ

精度に関係するもの

  • 話者の話し方(明瞭かどうか)
  • 部屋の反響

とのこと。興味深いですね。ICレコーダーやビデオカメラのマイクが駄目なのは、「部屋の反響」と関係しているのでしょうか。

一応、公式ページにも音質に関する言及があります。

cloud.google.com

曰く、

ノイズ低減

  • 雑音の多い音声も正常に処理できます。ノイズ除去の必要はありません。

とのこと。ただ、これはちょっとわかりづらいですね。「雑音」や「ノイズ」の定義が今ひとつ・・。

いずれにせよ、経験的には、ヘッドセットなどの吹き込み用マイクで録音した音声ファイルであれば、そうではないボワーッとした音声に比べてずっと綺麗にテキスト化されます。

ちなみに、ぼくが使っているヘッドセットは以下です。

この商品を選んだのは、日本のTech系ポッドキャストの草分けとも言える*5 Rebuild の@miyagawa さんのブログで紹介されていたからで、

weblog.bulknews.net

(今見たら2017年版は日本語だったのでそちらも・・)

weblog.bulknews.net

それからまる2年使っていますが、今もバリバリ現役で使えていて大変助かっています。

まとめ

ということで、例のごとく長文になりましたが、いかがでしたでしょうか。

Cloud Speech-to-Text、正直、多少のプログラミング知識なりターミナル操作の経験なりがないと実践するのは大変かなとも思いますが、大体こんな感じなのね、という全体像が伝わればひとまずよいかなと思っています。

あらためて要点だけ再掲すると、以前に紹介した、Googleドキュメントを使った場合に比べて挙げられるメリットは以下のような感じだと思います。

  1. 処理が走っている間に他の仕事をできる。
  2. 元の音声ファイルより短時間で処理が終わる。(現時点では3分の1ほど)
  3. 適宜改行が入る。(1分ごとに入る場合が多いが例外もありそう)

デメリットとしては、有料であること、それからくり返しになりますが、ある程度のプログラミングの素養が求められること、といったところでしょうか。

しかしそもそも、以前のGoogleドキュメント&Soundflowerによる文字起こしというのは、本来そのツールが想定している使い方からは少しズレたものだと思いますし*6、それに対してこのCloud Speech-to-Textの方はまさに文字起こしのために開発されているものですから、文字起こしをする用途であれば、こちらを利用する方が自然なのかなとも思います。

また、すでにこの音声認識エンジンを用いたWebサービスなどもあるようですから*7、プログラミングに馴染みがない人はそういったサービスを通してこの機能を利用するのもひとつの方法だと思いますし、逆にプログラマーの人であれば、ぼく以上にこの便利さを実感できるかもしれません。


(了)

*1:ファイルの一部だけを書き出したい場合は、その部分をAudacity内で範囲指定した上で「選択したオーディオの書き出し」にする。

*2:プロジェクトの作成方法はいろいろあると思いますが、ここではその一例を示します。

*3:しかしこういう分担をできるのがWeb記事のいいところですね。書籍でやったら各所から怒られる気がします。

*4:もちろん直接対象のファイル名を指定しても同じですが、手間が減るので自分ではこのようにしています。

*5:実際には「モダシンラジオ」やdrikinさんの一連の番組や長谷川恭久さんの「Automagic Podcast」などの先例もありますが、その後のフォロワーが続発したきっかけは「Rebuild」かな、という意味で。

*6:おそらくGoogleドキュメントの方はリアルタイム入力を前提にデザインされてるので。

*7:自分では試したことがないのでとくに紹介しませんが。