Excelを使った音楽ファイルのタグ付け

#はじめに
この記事はPython初心者の私が趣味のオーディオを関連付けてPythonの勉強をしようという試みの二歩目にあたるものです。
ちなみに1歩目は[こちら](https://qiita.com/salvawitch/items/90cbfaaa813dce4ba71c)。こちらで説明した内容はこの記事では省略します。

#概要
音源を管理していると、一曲ずつプロパティを開き、適切にタグをつけなおし、保存する作業が本当に面倒です。自動でデータベースからタグをつけてくれるソフトもありますが、アニメのキャラソンの新曲などになると全然あてになりません。そんな時、ExcelのようなGUIの上でタグの編集ができるソフトに出会いましたが、あくまで独自のGUIのため痒いところに手が届かずイマイチ求めているものと違うというのが本音でした。しかし、**Excel**の機能を用いてタグの編集ができればとても効率が上がります。そこで、Excelを利用した音源のタグ編集システムを前回に引き続き**Python3**と**Mutagen**というタグ編集用のモジュールを用いて実装していこうと思います。

今回は前回と同様に作業用のディレクトリを用意し、その中で作業を行うようなシステムを作りたいと思います。
対応する形式は私が普段使う**flac**,**mp3**,**aiff**を予定していますが、同様の方法で拡張できるようにしていきたいと思います。 

##csvについて
今回Excelでタグの編集を行うにあたって、**csv**という形式のファイルをExcelで開き、それを編集するという方法を取ろうと思います。csvとは、カンマと改行で数値を区切ったデータのことなんですが、この形式のファイルはExcelを用いても編集することができます。この時カンマで区切られた部分は列に、改行はそのまま行に対応してExcelで読むことができます。
具体的に、

```test.csv
1,artist1,album1,title1
2,artist2,album2,title2
3,artist3,album3,title3
```
のようなcsvファイルをExcelで開くと、セルA1は'1'、セルC2は'album2'のように読み込まれます。

#完成品
先にどんなものができたかお見せしますと、

```Python3:tag_table.py
# -*- coding:utf-8 -*-

import csv
import os
from mutagen.flac import FLAC
from mutagen.aiff import AIFF
from mutagen.easyid3 import EasyID3

def white_csv(filelist):
    with open('tags.csv', 'w', encoding='shift_jis') as f:
        writer = csv.writer(f, lineterminator='\n')
        writer.writerow(["ファイル名","アーティスト名","アルバム名","曲名"])

        for file in filelist:

            #tagsにタグのディクショナリを読み込む
            if file.endswith(".flac"):
                tags = FLAC(file)
                writer.writerow([file,','.join(tags['artist']), ','.join(tags['album']), ','.join(tags['title'])])
            elif file.endswith(".mp3"):
                tags = EasyID3(file)
                writer.writerow([file,','.join(tags['artist']), ','.join(tags['album']), ','.join(tags['title'])])
            elif file.endswith(".aiff") or file.endswith(".aif"):
                tags = AIFF(file)
                writer.writerow([file,','.join(tags['TPE1']), ','.join(tags['TALB']), ','.join(tags['TIT2'])])

def read_change_csv(filelist):
    with open('tags.csv', 'r', encoding='shift_jis', newline='') as f:
        tagslist = csv.reader(f)

        for row in tagslist:
            for file in filelist:
                if row[0] == file:
                    if file.endswith(".flac"):
                        music = FLAC(file)
                        music['artist'] = row[1]
                        music['album'] = row[2]
                        music['title'] = row[3]
                        music.save()
                    elif file.endswith(".mp3"):
                        music = EasyID3(file)
                        music['artist'] = row[1]
                        music['album'] = row[2]
                        music['title'] = row[3]
                        music.save()
                    elif file.endswith(".aiff") or file.endswith(".aif"):
                        music = AIFF(file)
                        music['TPE1'].text = row[1]
                        music['TALB'].text = row[2]
                        music['TIT2'].text = row[3]
                        music.save()

#
os.chdir('C:\\Users\\"ユーザー名"\\Music\\tag_box')
filelist = os.listdir(os.getcwd())

#対象フォルダ内の音楽ファイルを検出、ファイル名、タグを読み込み表にする
white_csv(filelist)

##表を変更
c = input("タグの入力が完了したらEnter")

#変更された表を読み込む
read_change_csv(filelist)

#完了の一言。
print("変更完了")
```
このようなコードを書きました。
これは、**ユーザーフォルダのMusic内にtag_boxという作業フォルダを作り、その中に編集したい音源を詰めてコードを実行すると、csvファイルが作られるので、それをもとに変更したい項目を編集してエンターを押すと、編集した通りcsvファイルの通りにタグを付け替えてくれます**

#実装の手順
1. 作業ファイル内の音楽ファイルを検出し、ファイルのリストを作成。
2. 検出した音楽ファイルの既存のタグ情報を読み込み、csvに書き出し。
3. 書き出したcsvファイルをもとにタグの編集したい項目を編集。
4. 編集したcsvを読み込み。
5. 読み込んだcsvからタグを編集。
(これはあくまでイメージなので、実際のコードはループなどでもうすこしだけ複雑になります。)

#コードの記述
まずは必要なモジュール類のインポート

```python:tag_table.py
import csv #csvファイルの読み書き
import os #Python内でのディレクトリ操作等
from mutagen.flac import FLAC #音源のタグ管理
from mutagen.aiff import AIFF
from mutagen.easyid3 import EasyID3
```

##2を行う関数の作成
```Python
def white_csv(filelist):
    with open('tags.csv', 'w', encoding='shift_jis') as f:
        writer = csv.writer(f, lineterminator='\n')
        writer.writerow(["ファイル名","アーティスト名","アルバム名","曲名"])

        for file in filelist:

            #tagsにタグのディクショナリを読み込む
            if file.endswith(".flac"):
                tags = FLAC(file)
                writer.writerow([file,','.join(tags['artist']), ','.join(tags['album']), ','.join(tags['title'])])
            elif file.endswith(".mp3"):
                tags = EasyID3(file)
                writer.writerow([file,','.join(tags['artist']), ','.join(tags['album']), ','.join(tags['title'])])
            elif file.endswith(".aiff") or file.endswith(".aif"):
                tags = AIFF(file)
                writer.writerow([file,','.join(tags['TPE1']), ','.join(tags['TALB']), ','.join(tags['TIT2'])])
```
フォルダ内のファイルのリストを引数とします。

オブジェクト指向の知識があいまいですが、わかる限りで自分用に細かく説明すると、
- `with open('tags.csv', 'w', encoding='shift_jis') as f:` 
  この部分はファイルを開き、書き込み用を指定し、ファイルに書き込むときの文字コードをShift-JISに指定しています。本当は自分の開発環境のUTF-8で統一したかったのですが、Microsoft製品の仕様でcsvファイルをデフォルトでUTF-8で開いてくれないので、しぶしぶShift-JISにしました。~~くたばれ~~。今のところ問題は起きてませんが、もしかしたらこのあたりが原因で文字化けが起こるかもしれません。~~くたばれ~~。
- `writer = csv.writer(f, lineterminator='\n')`
 この部分、おそらくPythonの肝ともいえるクラスオブジェクトの部分なのでしょうが、ちょっとよくわかりません。わかるのは、先ほどのfに書き込むとき、改行コードは'\n'を使ってねという意味程度です。難しいですね。
- `writer.writerow(["ファイル名","アーティスト名","アルバム名","曲名"])`
  この部分は実際にcsvファイルに文字列を書き込んでいる部分です。この順でこの先書き込んでいきます。ちなみにこの部分、実際Excelで開いてから並べ替えを行った際に少々邪魔になるので、気分で消してしまってもいいかもしれません。

この先は前回の応用で、if文で拡張子を判別し、ファイルを分別したのち、tagsという辞書型の変数にmutagenの関数を用いてタグの情報を格納。その後、先ほどと同じように`writerow`関数を用いてファイル名とタグ情報をcsvに記述しています。`','.join()`部に関しては、前回同様タグ情報から不要な部分を取り除くためのおまじないです。~~絶対もっといい方法があるとは思いますが~~。

##4,5を行う関数の作成
```Python3
def read_change_csv(filelist):
    with open('tags.csv', 'r', encoding='shift_jis', newline='') as f:
        tagslist = csv.reader(f)

        for row in tagslist:
            for file in filelist:
                if row[0] == file:
                    if file.endswith(".flac"):
                        music = FLAC(file)
                        music['artist'] = row[1]
                        music['album'] = row[2]
                        music['title'] = row[3]
                        music.save()
                    elif file.endswith(".mp3"):
                        music = EasyID3(file)
                        music['artist'] = row[1]
                        music['album'] = row[2]
                        music['title'] = row[3]
                        music.save()
                    elif file.endswith(".aiff") or file.endswith(".aif"):
                        music = AIFF(file)
                        music['TPE1'].text = row[1]
                        music['TALB'].text = row[2]
                        music['TIT2'].text = row[3]
                        music.save()
```

よく見たらこの関数細分化が適当すぎますね。私以外にはさほど需要もなさそうなプログラムなんで許してください・・・。

先ほどと同じ引数を用いています。
- `with open('tags.csv', 'r', encoding='shift_jis', newline='') as f:`
  この部分も先ほど同様です。変更後のcsvファイルを読み込んでいます。先ほどと違うところは、読み込みモードで行われている点と`newline=''`という引数が追加されている点です。これも、おまじないです。よくわからないけどいるらしいです。
- `tagslist = csv.reader(f)`
  読み込んだフォルダを、tagslistというリスト型の変数に格納しています。2次元配列のような形になっているんですかね？行(曲)ごとのタグのリストです。
-二重ループです。csvの編集時に、行の順番が変更されても良いように、ファイル名の部分をif文を用いてcsvファイルの中身と引数のfilelistとで照合し、一致した場合に他の要素をタグに代入し変更するようにしています。拡張子ごとに使用する関数が異なるので、ここでまたif文による拡張子の分別を行っています。

余談になりますが、**ID3**という形式のタグがmp3やAIFFでは用いられるのですが、これが、`TIT2(encoding=<Encoding.UTF16: 1>, text=['曲名'])`のような形になっています。手持ちのAIFFファイルは大方UTF16で記述されていたようです。mp3の場合、`EasyID3()`という関数のおかげで楽に操作できるんですが、AIFFの場合は直接ID3のタグを編集しなければなりません。これがなんなのか私にはさっぱりわかりませんでしたが、いろいろやっていると`music['TIT2'].text = row[3]`のような感じで書き換えが行えたので、一応何とかなりはしたのですが、文字コードがUTF-8のような気がします。いまのところバグはおきてませんが、第一引数も変更するべきかもしれません。

##mainにあたる部分の記述
あとは簡単です。

```Python3
os.chdir('C:\\Users\\"ユーザー名"\\Music\\tag_box')
filelist = os.listdir(os.getcwd())

#対象フォルダ内の音楽ファイルを検出、ファイル名、タグを読み込み表にする
white_csv(filelist)

##表を変更
c = input("タグの入力が完了したらEnter")

#変更された表を読み込む
read_change_csv(filelist)

#完了の一言。
print("変更完了")
```

1, 2行目は前回のそのままなので割愛。
`c = input("タグの入力が完了したらEnter")`の部分は変更待ちのためのものです。

以上！

#おわりに
正直自分にしてはかなり実用的なものが作れたと思います。以後は細分化をしっかりやりつつアートワークの貼り付けやジャンルの変更にも対応していこうと思います。