MP3タグ編集ツールの自作でスキルアップ(第2回・ソースコード解説編)

プログラミング入門
この記事は約15分で読めます。

今回はMP3タグ編集ツールの全ソースコードについて解説します。

前回の記事で説明した操作方法を念頭に、ソースコードに一通り目を通して頂いたあと、個々の解説を読んでいただくと分かりやすいかと思います。

また、Visual Studio 2019 のプロジェクトファイル一式を公開していますので、レイアウトの変更やソースコードの書き換え、機能の追記や変更を行い、動作がどう変わるか試して頂くと、より理解が深まると思います。

プロジェクト一式のダウンロード

プロジェクト一式(TagLib含む)は下記からダウンロードが可能です。

プロジェクトを任意のフォルダに解答の上、Mp3TagEditor.sln をダブルクリックするとVisual Studio 2019 が立ち上がり、ソースコードが表示されると思います。

もしTagLibの参照でエラーになるようなら、一旦TagLibをアンインストールの上、再度NuGetからインストールすることで解決します。

ソースコードの解説

それでは、さっそくソースコードを掲載しておきます。

今までに比べるとコード量が多いので身構えてしまうかもしれませんが、そんなに難しい事はしていません。

ここでは、ザックリと目を通して雰囲気を掴んでもらえればOKです。

ソースコードの解説

各行ごとに極力コメントを記載するようにしていますので、ソースコードと合わせて読んでいただくと理解しやすいと思います。

ただ、すべてを読んで理解する必要はありませんのでご安心下さい。

大切なのは、「どの部分を何処までコピーして、どこを変更したら自分のプログラムに組み込めるか」を理解する事です。

ソースコードを見ていて、少々分からない部分があっても、「なんとなくこういう事をしているんだ」という程度の理解が出来れば十分です。

それよりも、「ここからここまでが○○○を処理する部分だから、その部分を自分のソースコードにコピーしよう」とか、「この部分を自分のソースコードに取り込むのなら、この変数にはこの値を入れとく必要があるな」という観点で理解してもらうのが大切です。

こうすることで、他人が公開してくれているソースコードを流用する力が付いていきます。

前置きが長くなりましたが、今回公開したソースコードを自分様にカスタマイズしたり、あるいは皆さんのソースコードに流用する時のヒントになるような観点で、ポイントとなる部分を解説していきましょう。

コンストラクタとフォームロード処理

コンストラクタにはドロップダウンをMainFormの表示位置を設定するコードを書いています。

これらはVisual Studio のプロパティでも設定することが出来ますので、好みに合わせてもらえればOKです。

フォームロード(MainForm_Load)も

という記述がありますが、これらはコンストラクタに記述しても良いですし、 Visual Studio のプロパティを設定しても構いません。

フォームロードはコンストラクタの後に呼び出されるイベントで、画面が表示されるタイミングで呼び出されますので、

this.StartPosition = FormStartPosition.CenterScreen;

だけはフォームロードの前(つまりコンストラクタが実行される時)に設定しておく必要があります。

フォームロードでは、DataGridViewにドラッグ&ドロップの受付と行番号の表示に対応させるため、次の2行のコードを記述しています。

これらは他のプログラムで再利用しやすいように、メソッド化しています。

SetDragDropメソッド

DataGridViewにドラッグ&ドロップの機能を付加するプログラムです。

実際にドラッグ&ドロップした時の処理を17行目と20行目に記述していますので、必要に応じて書き換えてください。

DataGridViewに対して、Visual Studio から DragEnterとDragDropのイベントハンドラを作成し、そこに記述することも可能ですが、そうするとメソッド化できない(再利用しにくい)ため、今回はこのような記述をしています。

SetLineNumberメソッド

DataGridViewに行番号を付加する部分です。

これはGoogle検索で見つかったソースコードをそのまま掲載しています。

少し改造すると行番号の色を変えたり、別のセルに別の文字列を表示することも出来そうですが、単に行番号を表示したいというだけなら、このまま利用できます。

ShowDataメソッド

これはDataGridViewの体裁を整えつつ、DataTableの内容を表示するメソッドです。

引数に表示したDataTableを渡す仕様になっていて、8行目にDataGridViewのDataSourceプロパティに引数を代入しています。

これは8行目である必要はなく、メソッドの先頭に記述しても構いません。

ただ、列幅や列のReadOnly属性、列の入れ替え許可などの設定は、列が存在しないと出来ません。

逆に言うと、DataSourceにDataTableを代入した時点で新たな列が作られるため、その列に対して設定する必要があるので、ここだけは順番を気にする必要があります。

また、列幅やReadOnlyは設定したい値を配列に持たせて、Enumerable.Range を使ってuxMp3List.Columns[i] に代入していますが、これは好みの問題です。

以下の様に全ての列に対して、インデックス番号や列名を使って値を代入する方法もあります。

分かりやすさを優先するなら列名を使って個々の列を設定する方法が良いと思いますが、ソースコードの行数が長くなるので今回は採用しませんでした。

CreateDataTableメソッド

これはuxMp3List に表示するDataTableを作るためだけのメソッドです。

ShowDataメソッドで列幅を設定した時のように、列名を配列にしてEnumerable.Rangeで列を追加していく方法も出来ますが、別のソースコードに(列名を書き換えて)流用する際、Columnsの第2引数に変数の型を指定したい場合もあるため、今回はこのようにしました。

DataTableを作る部分をわざわざメソッドにしている理由は、フォームロードとドラッグ&ドロップの2か所で呼び出す必要があるからです。

プログラム起動時、uxMp3List に何も表示されないと寂しいので、せめて列名だけでも表示させたいと考えた時、フォームロードにも同じことを記述する必要が生じます。

それを避けるためメソッド化しました。

GetMp3Tagメソッド

SetDragDropメソッドの中で呼ばれていますが、ドラッグ&ドロップで受け取ったファイル名のMP3タグを読み出し、DataTableに格納して返すメソッドです。

最初にCreateDataTableメソッドでDataTableを作成しています。

次に、引数として渡されたファイル名の配列からファイル名を取り出し、 filelist に追加しています。

何故 filelist に追加しているかと言うと、渡されたファイル名が実はフォルダだった場合、そのフォルダ以下のファイルを全て取り出したかったからです。

その為、渡されたファイル名がフォルダか否かをチェックし、フォルダだったら配下のファイルを全て取り出して filelist に追加しています。

こうやってドラッグ&ドロップで渡されたファイルを全て取り出し、ループ処理にて1ファイルずつMP3タグを抽出、DataTableに追加しています。

メソッドの最後(リターンの直前)にAcceptChangesメソッドを読んでいます。

DataTableの各行(DataRow)はステータス(RowStat)を持っていて、その行が追加(Added)されたのか、変更(Modified)されたが分かるようになっています。

後にMP3ファイルに編集内容を書き戻す時、タグを編集したものだけを特定する際にRowStateが使えそうですが、そこには少しだけ工夫が必要です。

直前のforeach ループでMP3タグをDataTableに追加した訳ですから、今の RowState は追加状態(Added)になっています。

この状態でDataRowに変更を加えても、RowStat は Addedが優先されてModifiedにはならないのです。

このRowStateをリセットするメソッドが AcceptChanges であり、このメソッドを呼ぶことでDataTableに登録されている全てのDataRowのRowStateが リセット(Unchanged)されます。

こうすることで、以降このDataTableに対して変更を加えると、RowState が Modified になってくれます。

SetMp3Tag

引数で渡されたDataTableの内容(タグ情報)をMP3ファイルに書き込むメソッドです。

ポイントとしては、 foreach でループしながら DataRow を取り出し、RowStateが変更状態(Modified)である場合のみ、MP3ファイルにタグを書き戻すようにしている点です。

実際のMP3ファイルにタグを書き戻すには、Save メソッドを呼ぶだけで完了します。

タグを書き戻した次の処理は、ファイル名の変更処理です。

1列目のタイトル列と、最終列のファイルパスの内容を比較して、違っていたらタイトル列の内容でファイル名を変更しています。

ファイル名の変更はMoveメソッドを使います。

Moveメソッドはファイルの移動を行うメソッドですが、フォルダを同じにしておけば、ファイル名の変更になります。

変更後のファイル名を作るため、現在のフルパスからディレクトリ(フォルダ)と拡張子を取り出しているので、少々ややこしい記述になっています。

UTF2Jisメソッド

文字コードがShift-JISか否かを判定し、Shift-JISでなければ Shift-JISに変換するメソッドです。

MP3タグのタイトル、アーティスト名、アルバム名、コメントなどは日本語が登録できるのですが、MP3タグにもバージョンがあって、SHIFT-JIS で入っていたりUTF16で入っていたりと統一されていません。

これに対応するため、SHIFT-JISか否かを判定し、SHIFT-JIS でなければ SHIFT-JIS に文字コードを変換するようにしています。

具体的には、文字コードを一旦バイトの配列に変換して、再び SHIFT-JIS に変換し直した結果と、元々の文字列を比較しています。

もし元々の文字列がSHIFT-JISなら元に戻るはずなので、この方法で元に戻らなければ少なくともSHIFT-JISではなかったと判断できます。

もしSHIFT-JISでないと判断した場合、UNICODE から 一旦バイト配列に変換後、SHIFT-JIS に変換し直すという手順を踏むわけですが、バイト配列に変換すると1バイトごとに0x00 が付加されてしまいます。

このままだとSHIFT-JIS に正しく変換できないので、苦肉の策で 0x00を取り除く処理を入れています。

約1000ファイルの様々なMP3ファイルで試したところ、有名どころのフリーのMP3タグと同じ結果が得られたので、多分これで問題無いと判断しました。

フィルター実行ボタンイベントハンドラ

絞り込み処理は、簡易モード(uxIsEasy)にチェックが入っている場合、入力された文字から絞り込み条件を作る処理を行い、チェックが入っていなければ、入力された文字をそのまま絞り込み条件として使っています。

DataTableには DefaulView クラスがプロパティとして公開されており、RowFilterプロパティに条件を入れることで、DataTableの中身を絞り込んでくれます。

DataGridVIew(uxMp3List)のDataSource には DataTable が入っていますが、DataSourceはオブジェクト型なので、型変換を行ってDefaultViewのプロパティにアクセスしています。

簡易モードの時は、半角又は全角で分割し、「列名1」、「値1」、「列名2」、「値2」・・・という前提で2個ずつ取り出し、曖昧検索になるような条件式を作り出しています。

置換ボタン

ここは大きく2つの処理に分かれています。

1つは、フォーマット(uxFormat)の中身を展開しながら指定された列の全ての行に値をセットしていく処理、もう1つは入力されたフォーマットをドロップダウンリストに登録する処理です。

まず、指定された列の全ての行に値をセットしている処理を見てみましょう。

ここでは、DataGridView から 行(DataGridViewRow)を順番に取り出し、{列名} の文字列を、その列の値で置き換えているだけです。

ちなみに、DataGirdViewRowではなく、DataSourceからDataTableを取り出して、DataRowに対して変更を加えても良いのですが、DataGridViewのヘッダをクリックしてソートが行われると、DataGridViewとDataTableの行の並びが一致しなくなるので、ここではあえてDataGirdViewRowを使っています。

replace はこのメソッドの中で使うローカル関数です。

では、ローカル関数の中身を見ていきましょう。

第1引数はDataGridViewの列を保持しているコレクションクラス、第2引数は DataGridViewRow、第3引数は文字列ですが、ここは uxFormat.Text の値がセットされています。

そして、全ての列に対して以下の3ステップの処理を行うというのが、このローカル関数の役割になります。

  • DataGridViewColumnCollection から列名を取り出して前後を “{“、 “}” で挟み、置換前の文字列を作る
  • 次に、DataGridViewRow から 列名でセルを特定し、そのセルの値から置換後の文字列を作る
  • 第3引数で渡された文字列(uxFormat.Textの値)に対して、Replaceメソッドを使って文字列置換を行う

次はドロップダウンリストへの登録処理を見ていきましょう。

uxFormat.Text の値がドロップダウンリストに含まれているかを確認し、含まれていれば一旦削除してから、ドロップダウンの先頭に挿入しています。

こうすることにより、最新のフォーマット(最後に入力された uxFormat.Textの内容)が常にドロップダウンの先頭に来るようにしています。

文字列置換の実行イベントハンドラ

正規表現チェックボックス(uxUseRegular.Checked)にチェックが入っていると正規表現を使った文字列置換を行い、チェックが無ければ単純な文字列置換を行っています。

格安MP3プレーヤーの中には、曲の再生順がファイル名のソート順というケースがあります。

この場合、ファイル名の先頭に連番を付ける事が多いのですが、付けるのは簡単ですが、抹消するのは少々厄介です。

というのは、連番と曲名の間がピリオドだったりアンダーバーだったり、半角スペースだったりする可能性がありますし、桁も1桁~3桁混在していたり、先頭に0が付加されていたりと様々なパターンが考えられます。

これらのパターンに対応するには、正規表現を使うのが一番手っ取り早いのです。

ただ、正規表現はとっつき難く、複雑な条件を書くのは難易度が高いのが難点です。

でも、簡単な条件であれば少し勉強すれば使える様になりますし、使えると何かと便利なので、ここで少しだけ使ってみることにしました。

ちなみに、ここでも DataTable ではなく、DataGridViewに対して文字列置換を行っています。

フォームロード処理のところで、以下の様に初期値を設定しています。

最初の ‘^’ は、先頭を表しています。

次に ピリオド ‘.’ がありますが、これは任意の1文字を表し、アスタリスク ’*’ は直前の文字が0個以上繰り返し、はてな ‘?’ は直前の文字が0か1回あるという意味になります。

”[ -. ]” は、ハイフンかピリオドか半角スペースのいずれかという意味になります。

つまり、「先頭から数えて、任意の文字が1個以上連続で続いていて、ハイフンかピリオドか半角スペースのいずれかが1つ登場するまで」という意味になります。

 もし ‘?’ を抜いて “^.*[-. ]” と書くと、「任意の文字が1個以上連続で続いていてハイフンかピリオドか半角スペースが終わるまで」という意味になります。

ちょっとややこしいですが、”.*?” というパターンは良く使いますので、頭の片隅にでも入れておいていただければ、次に役立つと思います。

まとめ

以上でソースコードの解説は終了です。

かなり長くなりましたが、メソッドやイベントハンドラで処理が分割されているので、1つの処理に着目すれば理解しやすいかと思います。

何度も言いますが、全てを理解する必要はありません。

「こういう部分は他にも使えそう」とか、「他のプログラムに流用する場合は、ここからここまで切り出せば良いんだ」とか、そういう観点で理解してもらえれば良いかと思います。

そして、実際に中身をカスタマイズしたくなったら、その部分を集中的に理解するという方法が早道だと思います。

では、次の記事ではTagLib の使い方について解説したいと思います。

タイトルとURLをコピーしました