プログラマ英語学習日記

プログラミングと英語学習のまとめなど

簡単なコードを例に自分のプログラミング思考を詳細に追ってみた

例のGoogleDataAnalysisを終えて以来、ちょっと英語熱が再燃しておりちょっとずつ勉強を再開しました。

そこで一冊音読用の本を買ったのですが、音声ファイルが「文章1つで1トラック」になっておりめちゃくちゃ使いづらい。1500ファイルぐらいありいくらなんでもiPhoneで探しづらい。

というわけで 同じ章にある音声をまとめるプログラム を作りました。ここではその過程を「プログラミング思考とは?」を題材に解説していきます。

普段は無意識的に行っている「コードを書くときの自分の思考回路」を文章化したものです。本当にプログラム未経験の人向け。経験者は見る価値ない記事です(笑)。プログラミングってこういうふうに考えていくんだ、という理解になれば幸いです。

教材

教材は TOEFL iBTテスト必修英文法50。文法が50の章に分かれて解説されており、各章に10弱の英文があります。そして冒頭で述べたように、文章一つ一つにmp3があります。つまり膨大な数になります。 そんなこと知らずにiTunesにフォルダごと取り込んで端末に転送したのでビックリしました。大量にトラックがあってとてもじゃないですが探し出せません。

そこで「章ごとにmp3ファイルを連結し、一個のmp3ファイルにする」構想を立てました(全体として50ファイル)。もちろんプログラムを使って。以降の記事は技術でなく「これを作るときの自分の思考」にフォーカスしてかつ詳細に述べていきたいと思います。何かの参考になれば、プログラミング的思考の理解になれば幸いです。

教材のレビューはまた別の記事を書きます。TOEFL関係なくいい本です(レベルが中学文法程度でTOEFLガチ勢には物足りないと思う)

やりたいことを分解する

まずはやりたいことを明確化します。「mp3ファイルを章ごとにまとめたい」では大雑把すぎます。 (補足:普段はこれぐらい大雑把な状態でコード書いてますw 普段は無意識レベルで行ってる思考を文章化してます)

もう少し詳細に述べると

  • 大量にあるmp3ファイルを
  • 章ごとに分類して
  • 各分類ごとに音声ファイルを結合し
  • 新しいmp3ファイルとして出力する

というステップに分解できます。

課題を見つける

上を見たときに自分は2つ技術的な課題があると考えました

  • mp3の読み込み・連結処理をどう行うか。そもそも(楽に)できるのか
  • どうやって「同じ章」であると判断するか

楽に、がついたのは趣味だからです。手間かけて数ヶ月かけてmp3デコーダー読み込めばいけはするはず。ただしやりたくないw

これらを克服しないと上記プログラムは生成できません。このためここの技術的課題をクリアできるかを先に検証する必要があります。

それぞれについて説明します。

mp3

まずmp3についての問題を解決します。というのも ここをクリアしないとプロジェクトが頓挫するからです。

2個めの課題のファイル分類は面倒なプログラムを書けばまぁなんとかなりそうですが、mp3結合は誰かの作ったライブラリの助けがないとまず無理です。少なくとも作りたくない。なのでまずこのmp3読み込み/結合ができるかを調べます。


といってもこれはプログラミング言語や環境により異なります。極端な話、これをAndroid端末でしたい!となったらAndroid標準の命令群から探し出す必要があります。環境面の制約を考える必要があります。仮に仕事だと、「仕事で使っているサーバ/言語で動く」なども制約でしょう。

今回は趣味。自分のパソコンで動けばOKです。つまり

  • Windowsパソコンで実行する
  • Windowsで動けば言語/ライブラリは問わない
  • とはいえ未経験の言語にトライするほど時間はかけたくない

となります。そんなわけで、自分のスキルのうち一番ラクにできそうな言語/ライブラリ を探します。ここはググるしかない。


結論からいいますと、

ことで手軽にできそうです。内部的にFFmpegを使ってるだけなのでShellScript+FFmpegでもいけそうですが、ShellScriptは苦手なのでPythonにしました。ここは人によると思うので正解があるわけではありません。 今回は pydubが楽そうだからPythonにした だけで、Pythonありきだったわけではありません。仮に自分のスキルセットがJavaだけだったらJavaのmp3関係ライブラリを意地でも探してたと思います。ここは個々人の好きなように、です。

検証コード

とはいえいきなり本番コードを書いてはいけません。使ったことがないライブラリなので、まずは軽く「2つのmp3を読み込んで結合する」実験から行います。

最初に小さいサンプルを書いて実験するのはすごい大事です。 特に未知の言語/ライブラリでは。 いきなり「ファイル分類+mp3結合」のコードを書くと、バグったときにちょっとした書き方のミスなのか、根本的に使い方がおかしいのか分かりません。まして正しい使い方を知らないライブラリならなおさらです。このためまずは小さいサンプルで実験し、「自分の予想どおりの結果を出すにはどういうコードを書けばいいか」を調べます。

そんなわけでまずは2つのmp3を合体させます。思ったより簡単だったのでいきなりコード。

from pydub import AudioSegment

sound1 = AudioSegment.from_file("0101.mp3")
sound2 = AudioSegment.from_file("0102.mp3")
sound = sound1 + sound2
sound.export("output.mp3", format="mp3")

無事生成できました。簡単ですね、素晴らしいpydub。こういうサクッとしたコードはpythonに限ります。

これで技術課題の一つは解決しました。山場は突破です。

章ごとに分類

第二の課題が「ファイルを章ごとに分類する」作業です。 プログラム経験がない人だと、「章ごとに分類なんて楽勝じゃん」と考えたかもしれません。では以下のようにファイルがあったらどう章ごとに分類しますか?

※実際の教材とは別。架空のものです
1.mp3
2.mp3
..略...
100.mp3

どこが章の区切りかわかりませんね。つまりファイル名をベースに章ごとに分類できません。 こうなると別の情報を見て分類する必要があるわけです。「mp3のタグ情報」だったり「各章に何個の文章があるか自分でリストを作る」でもいいでしょう。章ごとにフォルダに分類されていたらもっと楽ですね。

つまり 「何を使って同じ章とするか」というのを決めなければいけません。 別の言い方をすると たくさんのファイルとにらめっこして、そこに潜む規則性を発見する必要があります。そしてその規則を使い分類するのです。(規則がないならこちらからデータを与える、各章の文章数入力はコレ)


今回の教材のファイルをみてみましょう。

f:id:nexus1:20210529131617p:plain

右側にmp3タイトルがでてますね。これで分類してもいいですが、よく見ると「ファイル名の先頭2文字が章、末尾2文字が各章での順番」であることがわかります。 mp3の内容を読み込むのは手間かかるので、今回はファイル名を見ます。つまり

同一フォルダにある大量のファイルを、先頭2文字ごとにグルーピングする

ことで章ごとに分類できることがわかりました。


やはりこちらもコードに落とし込んでみましょう。さらに詳細に分けると2つに分解でき

  • 特定フォルダにあるファイル一覧を取得
  • それらを先頭2文字で分類する

という作業です。今回はまずprintで表示しましょう。

import os
from itertools import groupby

files = os.listdir("src/Listening/")
grouped = groupby(files, key=lambda f: f[0:2])
for key, group in grouped:
    print(key + "------------")
    for file in group:
        print(file)

Pythonは楽だなぁ(笑)。こういうサクッとしたツールは本当に簡単にかけていい。


そんなわけで2つの技術的課題をクリアし、プログラムに必要なパーツが揃いました。実証実験が終わり、いよいよコード作成に取り掛かります。

コーディング

やることは大きく分けて2つに過ぎません

  • 特定のフォルダからファイル一覧を読み込んで分類
  • 分類された情報をもとにmp3連結

それぞれを関数にすると良さそうです。

  • フォルダのパスを引数として受け取り、分類されたファイル一覧を配列で返す関数
  • ファイル一覧を引数として受け取り、各ファイルを連結・指定パスに出力する関数

もちろんもっと細かい関数にしてもいいですし、一個の関数で閉じてしまってもOKです。今回はこの程度でいきましょう。

Javaのような架空の言語で記述するなら次のようなものでしょうか。(PythonよりJavaのほうが型がわかりやすいので)

String[][] readFileList(String inputDir) {...}
void joinAndWrite(String[] files, String outputMp3Path) {..}

void main() {
    String[][] group = readFileList("./inputDir");
    for (int i = 0; i < group.length; i++) {
        String output = "output" + i + ".mp3";
        String[] files = group[i];
        joinAndWrite(files, output);
    }
}

それぞれの関数がほぼ実証コードまんまですね(笑)。あとは移植する程度なので省略します。


まとめ

簡単なコードではありますが、プログラミング思考というのを書いてみました。自分の無意識レベルの思考を文章に起こしながらコードを書くというのはなかなかおもしろい経験でした。正直コードを書く時間の5倍ぐらい記事に時間かけてます(笑)

個人的にはプログラミングの本質は「コードを書く」ことではなく、こういう 問題を細分化していく思考回路 のことだと思ってます。広い意味での「アルゴリズム」ですね。未経験の人の参考になればと思います。