久々にPythonでプログラム書いたのでメモ.

仕事でテストデータ作るのが面倒だったのでw ツールを作成してみました.
何がしたいかというと,.mp3ファイルがたくさん欲しかったので,IDv3タグを変更してIdv3タグ的に別のアーティスト・アルバムにしたかったのです.

IDv3タグをいじれるライブラリを探したらmutagenというのを発見したので,
早速ダウンロードして使ってみました.

作ったソースはこんな感じです. Python慣れてないから,変なことしてるかも知れないけど・・・
#エラー処理とかはちゃんとやってません.自分で使うのがメインだし.

#!/usr/bin/python

import os, sys, time
import threading

sys.path.append('mutagen')
from mutagen.mp3 import MP3
from mutagen.easyid3 import EasyID3
import mutagen.id3

class ExcuteWriting(threading.Thread):
	def __init__(self, tagData):
		threading.Thread.__init__(self)
		self.tagData = tagData
		
	def run(self):
		
		target_dir =  self.tagData.result_dir + "/" + "artist_" + str(self.tagData.artist)
		create_dir(target_dir)

		for album in range(int(self.tagData.album_max)):
			for track in range(int(self.tagData.track_max)):
				filename = target_dir + "/" + "album_" + str(album) + "_" + str(track + 1) + ".mp3"
				print "Creating %s...\n" % filename
				self.copy_file(self.tagData.base_file, filename)
				self.write_mp3_tag(filename, album)
	
	def write_mp3_tag(self, filename, album):
		m = MP3(filename, ID3=EasyID3)

		m.add_tags(ID3=EasyID3)

		m['title'] = "Title" + str(self.tagData.artist)
		m['artist'] = str(self.tagData.artist)
		m['album'] = str(album)
		m.save()
		m.pprint()
#		print(m.pprint())


	# Read base file and copy it.
	def copy_file(self, old_name, new_name):
		try:
			fr = open(old_name, "rb")
			fw = open(new_name, "wb")
			
			fw.write(fr.read())
			
			fr.close()
			fw.close()
			
		except :
			print "file read/write error occured\n"


class TagData:
	def __init__(self, base_file, artist, album_max, track_max, result_dir):
		self.base_file = base_file
		self.artist = artist
		self.album_max = album_max
		self.track_max = track_max
		self.result_dir = result_dir



def create_dir(ret_dir):
	# if we do have result dir, we won't create it.
	if (os.access(ret_dir, os.F_OK) == False):
		os.mkdir(ret_dir)
	
if __name__ == "__main__":

	# At first, we have to check command line arguments.
	if (len(sys.argv) != 6):
		print "usage: id3writer [base file] [output directory] [n artist] [n album] [n tracks]\n"
		exit (0)

	base_file = sys.argv[1]
	ret_dir = sys.argv[2]
	artist_max = sys.argv[3]
	album_max = sys.argv[4]
	track_max = sys.argv[5]

	# Do we have result dir?
	create_dir(ret_dir)
	
	for artist_num in range (int(artist_max)):
		# Max thread is 10.
		if (threading.activeCount() > 10):
			while threading.activeCount() > 10:
				time.sleep(0.5) 

		tagData = TagData(base_file, artist_num, album_max, track_max, ret_dir)
		worker = ExcuteWriting(tagData)
		worker.start()

大量にデータ作るのにシーケンシャルにやってたら時間かかりそうなので,スレッドも初めて使ってみました.
このプログラムが行うのは,1アルバム辺りNトラックあるアルバムをM個,Kアーティスト分作成するという内容です.

例えば,1アルバムに2トラック・1アーティストに2アルバムというのを2アーティスト分作るとこんな感じです.

[masami@moonlight:~/experiment/id3writer]% ./id3write.py base.mp3 result 2 2 2          
Creating result/artist_0/album_0_1.mp3...

Creating result/artist_1/album_0_1.mp3...

Creating result/artist_0/album_0_2.mp3...

Creating result/artist_1/album_0_2.mp3...

Creating result/artist_0/album_1_1.mp3...

Creating result/artist_1/album_1_1.mp3...

Creating result/artist_0/album_1_2.mp3...

Creating result/artist_1/album_1_2.mp3...

[masami@moonlight:~/experiment/id3writer]% ls -ls result/*
result/artist_0:
合計 2360
  4 drwxr-xr-x 2 masami masami   4096 Jan  9 00:19 ./
  4 drwxr-xr-x 4 masami masami   4096 Jan  9 00:19 ../
588 -rw-r--r-- 1 masami masami 598595 Jan  9 00:19 album_0_1.mp3
588 -rw-r--r-- 1 masami masami 598595 Jan  9 00:19 album_0_2.mp3
588 -rw-r--r-- 1 masami masami 598595 Jan  9 00:19 album_1_1.mp3
588 -rw-r--r-- 1 masami masami 598595 Jan  9 00:19 album_1_2.mp3

result/artist_1:
合計 2360
  4 drwxr-xr-x 2 masami masami   4096 Jan  9 00:19 ./
  4 drwxr-xr-x 4 masami masami   4096 Jan  9 00:19 ../
588 -rw-r--r-- 1 masami masami 598595 Jan  9 00:19 album_0_1.mp3
588 -rw-r--r-- 1 masami masami 598595 Jan  9 00:19 album_0_2.mp3
588 -rw-r--r-- 1 masami masami 598595 Jan  9 00:19 album_1_1.mp3
588 -rw-r--r-- 1 masami masami 598595 Jan  9 00:19 album_1_2.mp3
[masami@moonlight:~/experiment/id3writer]% 

スレッドを使うときは"threading.Thread"を継承する.継承の仕方はクラス名の後に(threading.Thread)を付けると.

class ExcuteWriting(threading.Thread):

コンストラクタからthreading.Threadの__init__を呼んであげる必要があります.

	def __init__(self, tagData):
		threading.Thread.__init__(self)

そうしたら,スレッド動かすだけなので,
クラスのインスタンスを生成して,startメソッドを呼べばOKです.

worker = ExcuteWriting(tagData)
		worker.start()

start()からExcuteWritingのrunメソッドが呼ばれるので,runメソッドは必須です.
今回は使ってないですけど、class localってTLSみたいなもんですかね?
リファレンスによるとクラスに対して使う感じのようです.

 foo = threading.local()
 foo.x = 10

あと,無制限にスレッド作るのもなんなので,threading.activeCount()というメソッドを使って,
動いているスレッド数は最大10個までという感じにしました.

		if (threading.activeCount() > 10):
			while threading.activeCount() > 10:
				time.sleep(0.5) 

スレッド周りは大体こんな感じです.

実際にIDv3タグを弄るところはこんな感じになってます.
MP3クラスのインスタンスを作って・・・

		m = MP3(filename, ID3=EasyID3)

		m.add_tags(ID3=EasyID3)

タグに対して値をセットします.
trackを試したら"無い"と怒られました(つд⊂)エーン

		m['title'] = "Title" + str(self.tagData.artist)
		m['artist'] = str(self.tagData.artist)
		m['album'] = str(album)

最後にセーブすれば完了です.

		m.save()

スレッド使ったり,IDv3タグ弄るライブラリ使っても,たったこれだけのコードで必要な処理ができるので
Python便利です!