LoginSignup
3
2

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-04-18

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

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を知りませんでした。10行程度でしたがかなり頭を捻らせて考えました。
書き終えた時はとても嬉しかったのですが、globを知ってしまいました。
1行で書けちゃいました...
悲しかったです...

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2