【Python】iTunesの曲を全てコンバートした。

# Introduce
初投稿です。
曲を解析し近似したものをユーザーに提供するアプリケーションを作っています。
以下のコードは、解析する曲を準備する段階のものです。
iTunesライブラリにある曲をwavファイルに変換し、別のディレクトリにコピーするコードを書きました。
スパゲティって言わないでください。わかってます。

# Flowchart
iTunesライブラリに曲が増えた場合や消された場合、全てをコンバートし直すには時間がかかり同じタイトルを再度コンバートすることになる為、1回目と2回目以降の実行の挙動を変えることで効率化を図りました。
1回目の実行かの判断は、コピー先のディレクトリが存在しているかを基準にしています。
同時に、ユーザーがcsvを消した場合、2回目の実行が不可能になる為csvの存在も基準にしています。
csvは、保護された曲のタイトル名を保存しています。1回目の実行結果を2回目の実行に引き継ぐ方法を知らないので止むを得ずcsvを使用しました。2回目の実行では、保護されたタイトルを引いてからiTunesライブラリとコピー先のディレクトリの比較をすることで整合性を保っています。

1回目の実行
1. コピー先のディレクトリを生成
2. iTunesライブラリに存在するタイトルのパスをglobで取得。
3. コンバート可能な曲は、コピー先のディレクトリに出し不可のものは絶対パスをcsvへ格納
4. iTunesライブラリとコピー先のディレクトリを比較し正常に動作したかを確認
5. 終了

2回目の実行
1. コピー先のディレクトリとcsvファイルの存在を確認。どちらか存在しない場合は1回目の処理を実行
2. iTunesライブラリとコピー先のディレクトリをglobして比較。Trueであれば終了。Falseは続行
3. iTunesライブラリに増減があった場合、コピー先のディレクトリの中にコピー若しくは削除
4. 再度、iTunesライブラリとコピー先のディレクトリの比較を行う。Falseであれば上の行を実行。
5. 終了

# Code

```Python:copymusic.py
import os
import shutil
import glob
import csv
from pydub import AudioSegment,exceptions

homedir    = os.environ['HOME']
prj_dir    = '{}/Desktop/project'.format(homedir)
new_dir    = '{}/Desktop/project/wav'.format(homedir)
artist_dir = '{}/Music/iTunes/iTunes Media/Music/'.format(homedir)
wav_tree   = '{}*/*'.format(new_dir)
music_tree = '{}*/*/*'.format(artist_dir)
csv_path   = '{}/DONOTTOUCH.csv'.format(prj_dir)
extension  = ['.m4a','.mp3','.wav','.aac','.aiff']
drm_title_list = []
flag       = 0

def initialize():
    os.makedirs(new_dir)
    for path in glob.glob(music_tree):
        title = os.path.basename(path)
        title_extension = os.path.splitext(title)[1]
        re_extension_path = os.path.join(new_dir,'{}.wav'.format(os.path.splitext(title)[0]))
        if title_extension == '.m4a':
            try:
                song = AudioSegment.from_file(path)
            except exceptions.CouldntDecodeError:
                drm_title_list.append(path)
            else:
                song.export(re_extension_path,format='wav')
        if title_extension == '.mp3':
            try:
                song = AudioSegment.from_mp3(path)
            except exceptions.CouldntDecodeError:
                drm_title_list.append(path)
            else:
                song.export(re_extension_path,format='wav')
        if title_extension == '.wav':
            shutil.copy(path,os.path.join(new_dir,title))
        if title_extension == '.aac':
            try:
                song = AudioSegment.from_file(path)
            except exceptions.CouldntDecodeError:
                drm_title_list.append(path)
            else:
                song.export(re_extension_path,format='aac')
        if title_extension == '.aiff':
            try:
                song = AudioSegment.from_file(path)
            except exceptions.CouldntDecodeError:
                drm_title_list.append(path)
            else:
                song.export(re_extension_path,format='aiff')
    with open(csv_path,'w') as f:
        writer = csv.writer(f)
        writer.writerow(drm_title_list)
    return print('Copy completion')

def title_list(path_list):
    """
    ディレクトリパスのリストを引数とし、拡張子の省いた曲名だけが返される。
    :param path_list:   list glob.glob(path).
    :return:            list song title.
    """
    titles = []
    for path in path_list:
        basename = os.path.basename(path)
        if os.path.splitext(basename)[1] in extension:
            title = os.path.splitext(basename)[0]
            titles.append(title)
    return set(titles)

def copy_title(add_title):
    for title in add_title:
        for path in music_path_set:
            if title in path:
                basename = os.path.basename(path)
                title_extension = os.path.splitext(basename)[1]
                re_extension_path = os.path.join(new_dir,'{}.wav'.format(title))
                if title_extension == '.m4a':
                    try:
                        song = AudioSegment.from_file(path)
                    except exceptions.CouldntDecodeError:
                        drm_title_list.append(path)
                    else:
                        song.export(re_extension_path,format='wav')
                if title_extension == '.mp3':
                    try:
                        song = AudioSegment.from_mp3(path)
                    except exceptions.CouldntDecodeError:
                        drm_title_list.append(path)
                    else:
                        song.export(re_extension_path,format='wav')
                if title_extension == '.wav':
                    shutil.copy(path,os.path.join(new_dir,title))
                if title_extension == '.aac':
                    try:
                        song = AudioSegment.from_file(path)
                    except exceptions.CouldntDecodeError:
                        drm_title_list.append(path)
                    else:
                        song.export(re_extension_path,format='aac')
                if title_extension == '.aiff':
                    try:
                        song = AudioSegment.from_file(path)
                    except exceptions.CouldntDecodeError:
                        drm_title_list.append(path)
                    else:
                        song.export(re_extension_path,format='aiff')

def rm_title(titles):
    for title in titles:
        extension_add_title = '{}.wav'.format(title)
        rm_title_path = os.path.join(new_dir,extension_add_title)
        os.remove(rm_title_path)


if not os.path.isdir(new_dir) or not os.path.exists(csv_path):
    flag = flag + 1
    initialize()
with open(csv_path) as f:
    reader = csv.reader(f)
    for row in reader:
        drm_title_list = row
music_path_set = set(glob.glob(music_tree))
for drm_title in drm_title_list:
    music_path_set.remove(drm_title)
wav_path_set   = set(glob.glob(wav_tree))
music_set = title_list(music_path_set)
wav_set = title_list(wav_path_set)
while music_set != wav_set:
    if music_set > wav_set:
        titles = music_set - wav_set
        copy_title(titles)
    elif music_set < wav_set:
        titles = wav_set - music_set
        rm_title(titles)
    music_path_set = set(glob.glob(music_tree))
    for drm_title in drm_title_list:
        music_path_set.remove(drm_title)
    wav_path_set   = set(glob.glob(wav_tree))
    music_set = title_list(music_path_set)
    wav_set = title_list(wav_path_set)
if flag == 0:
    print('差異なし')
```

## Commentary
###### initialize()
iTunesライブラリにあるコンバート可能な曲をコピーする関数です。
愚直で大量の分岐処理は、拡張子毎に使用するモジュールが違うためこのようになりました。
各分岐にある例外処理は、保護された曲をコンバートした際のエラーを条件にリストに格納しています。
このリストは、全てのコンバートが終わった後にcsvに書き出されます。
この部分は改良の余地がありますが、スパゲティを美味しくするためのソースだと思えば問題ありません。

###### title_list()
globで取得したパスからファイル名のみをset型で返す関数です。
globは、対象のディレクトリのパスを全て取ってきてくれます。
実行結果はこのようなものです。ーー＞「hoge/fuga/piyo.m4a」,「hoge/fuga/piyo.mp3」,「hoge/fuga/piyo.txt」
このような結果が返ってきては、iTunesライブラリとコピー先のディレクトリをglobしただけでは比較が取れません。そこで、この関数を通すことで拡張子とパスを省いた純粋なファイル名「piyo」を取得し結果同士を比較することができます。

###### copy_title()
効率化の肝となる関数です。
iTunesライブラリに新しい曲が追加された際、その曲のみをコピー先のディレクトリにコンバート＆コピーします。

###### rm_title()
copy_title()と同様に、効率化の肝となる関数です。
iTunesライブラリから曲が消された場合、その曲のみをコピー先のディレクトリから削除します。

# P.S
このコードを書き始めた時、iTunesライブラリの曲の全て取ってくるところから始まりました。
その時はglobを知りませんでした。１０行程度でしたがかなり頭を捻らせて考えました。
書き終えた時はとても嬉しかったのですが、globを知ってしまいました。
１行で書けちゃいました...
悲しかったです...