Pythonで自分の持っているCDの分析する(VOCALOID版)

・曲をそれぞれ解析し、アーティスト,歌手,曲の重複数など順位を調べる

環境

Windows11

Visual Studio 2022 (Community)

Python 3.9(64bit)

注意点(VOCALOID曲特有の部分中心)

変数の準備など

# -*- coding: shift-jis -*-は、パスに日本語(マルチバイト文字)がある場合に必要

import os を先に書く


import os
# -*- coding: shift-jis -*-
dir_path = r"ミュージックフォルダまでのパス"
files = []
res = {}
res2 = {}
songs = {}
		

mp3ファイルを取得

ジャケット画像を設定している場合はjpgファイル、ミュージックフォルダにはdesktop.iniがあるので拡張子からそれらを弾く

自分の持っているファイルの曲番号は2桁か3桁(01,001など)なので、空白位置からどっちか調べ、番号を消した状態で曲名をsongsに追加する(曲別ランキングに使用)


def add_file(path):
	for name in os.listdir(path):
		if len(name) > 4:
			if name[-4:] == ".mp3":
				if name[2] == ' ':
					if name[3:-4] in songs:
						songs[name[3:-4]] += 1
					else:
						songs[name[3:-4]] = 1
				elif name[3] == ' ':
					if name[4:-4] in songs:
						songs[name[4:-4]] += 1
					else:
						songs[name[4:-4]] = 1
				files.append(path + "\\" + name)
				continue
			elif name[-4:] == ".jpg" or name[-4:] == ".ini":
				continue
		add_file(path + "\\" + name)
		

mp3ファイルのTPE1タグを取得

TPE1タグにアーティスト情報が載っている(○○ Feat.△△ など)

TPE1タグを取得するライブラリは存在するが、文字化けするので使用しない。

なので、自力でバイナリーデータから取得し、文字コードの変換をする。

検索用のtarget ("TPE1")は、UTF-8でエンコードしておけば何とかなる

今のところ、文字コードは UTF-8,Shift-JIS,UTF-16のいずれかで対応できたので、try-exceptを使いしらみつぶしに変換する

ヌル文字はここで削除する(utf-16を除く)


def get_tpe1(path):
target = "TPE1".encode("utf-8")
with open(path,"br") as f:
	data = f.read()
	i = data.find(target) + 7
	tmp = data[i + 4:i + 3 + data[i]]
	rt = ""
	if not i == -1:
		try:
			return tmp.replace(b"\x00",b"").decode("utf-8")
		except Exception:
			try:
				return tmp.replace(b"\x00",b"").decode("shift-jis")
			except Exception:
				return tmp.decode("utf-16")
	else:
		return ""
		

最終仕上げ

取得したデータをソートして、ランキング(多い順)で表示する

アーティスト名とFeat以降の区切りを見つける

アーティスト名のみ(l = -1 + 5 つまり4)の場合は、アーティスト名のみを取得

replace()を使いアーティスト名の統一、名前の区切りの統一をしておく(例:ゴジマジP→ラマーズP Feat.初音ミク&鏡音リン→Feat.初音ミク,鏡音リン)

このプログラムでは、すべてのアーティスト名の別名には対応していないので注意(多い/有名なものだけ) すべて対応するのは非常に大変


add_file(dir_path)
print("曲数: ",len(files))
print("種類: ",len(songs))
for path in files:
	ats = get_tpe1(path)
	if not ats == "":
		if "loves" in ats:
			l = ats.lower().find("loves") + 5
		else:
			l = ats.lower().find("feat.") + 5
		if not l == 4:
			artist = ats[:l - 6] if ats[l - 6] == " " else ats[:l - 5]
			artist = artist.replace("ryo","supercell").replace("ゴジマジP","ラマーズP").replace("ピノキオP","ピノキオピー").replace("ちょむP","TakeponG (ちょむP)").replace("1640mP","40mP").replace("じん (自然の敵P)","じん").replace("wowaka (現実逃避P)","wowaka").replace("マチゲリータP","マチゲリータ")
			feats = ats[l:].replace(" ","").replace("x",",").replace("&",",").replace("+",",").replace("・",",").replace("、",",")
			if ("鏡音リン" in feats) and (not ("鏡音レン" in feats)) and ("レン" in feats):
				feats = feats.replace("レン","鏡音レン")
			featsl = feats.split(",")
			if artist in res2:
				res2[artist] += 1
			else:
				res2[artist] = 1
			for feat in featsl:
				if feat in res:
					res[feat] += 1
				else:
					res[feat] = 1
		elif not len(ats) == 0:
			artist = ats[:-1] if ats[-1] == " " else ats
			artist = artist.replace("ryo","supercell").replace("ゴジマジP","ラマーズP").replace("ピノキオP","ピノキオピー").replace("ちょむP","TakeponG (ちょむP)").replace("1640mP","40mP").replace("じん (自然の敵P)","じん").replace("wowaka (現実逃避P)","wowaka").replace("マチゲリータP","マチゲリータ")
			if artist in res2:
				res2[artist] += 1
			else:
				res2[artist] = 1
sigma = [sum(res.values()),sum(res2.values())]
res = sorted(res.items(), key = lambda x : x[1], reverse = True)
res2 = sorted(res2.items(), key = lambda x : x[1], reverse = True)
songs = sorted(songs.items(), key = lambda x : x[1], reverse = True)
print("")
print("歌手別ランキング")
for result in res:
	print(result,"  ",result[1] / sigma[0] * 100,"%")
print("")
print("アーティスト名ランキング")
for result2 in res2:
	print(result2,"  ",result2[1] / sigma[1] * 100,"%")
print("")
print("曲別ランキング")
for song in songs:
	print(song)
input()
		

完成形


import os
# -*- coding: shift-jis -*-
dir_path = r"C:\Users\森悟\Music"
files = []
res = {}
res2 = {}
songs = {}
def add_file(path):
	for name in os.listdir(path):
		if len(name) > 4:
			if name[-4:] == ".mp3":
				if name[2] == ' ':
					if name[3:-4] in songs:
						songs[name[3:-4]] += 1
					else:
						songs[name[3:-4]] = 1
				elif name[3] == ' ':
					if name[4:-4] in songs:
						songs[name[4:-4]] += 1
					else:
						songs[name[4:-4]] = 1
				files.append(path + "\\" + name)
				continue
			elif name[-4:] == ".jpg" or name[-4:] == ".ini":
				continue
		add_file(path + "\\" + name)

def get_tpe1(path):
	target = "TPE1".encode("utf-8")
	with open(path,"br") as f:
		data = f.read()
		i = data.find(target) + 7
		tmp = data[i + 4:i + 3 + data[i]]
		rt = ""
		if not i == -1:
			try:
				return tmp.replace(b"\x00",b"").decode("utf-8")
			except Exception:
				try:
					return tmp.replace(b"\x00",b"").decode("shift-jis")
				except Exception:
					return tmp.decode("utf-16")
		else:
			return ""

add_file(dir_path)
print("曲数: ",len(files))
print("種類: ",len(songs))
for path in files:
	ats = get_tpe1(path)
	if not ats == "":
		if "loves" in ats:
			l = ats.lower().find("loves") + 5
		else:
			l = ats.lower().find("feat.") + 5
		if not l == 4:
			artist = ats[:l - 6] if ats[l - 6] == " " else ats[:l - 5]
			artist = artist.replace("ryo","supercell").replace("ゴジマジP","ラマーズP").replace("ピノキオP","ピノキオピー").replace("ちょむP","TakeponG (ちょむP)").replace("1640mP","40mP").replace("じん (自然の敵P)","じん").replace("wowaka (現実逃避P)","wowaka").replace("マチゲリータP","マチゲリータ")
			feats = ats[l:].replace(" ","").replace("x",",").replace("&",",").replace("+",",").replace("・",",").replace("、",",")
			if ("鏡音リン" in feats) and (not ("鏡音レン" in feats)) and ("レン" in feats):
				feats = feats.replace("レン","鏡音レン")
			featsl = feats.split(",")
			if artist in res2:
				res2[artist] += 1
			else:
				res2[artist] = 1
			for feat in featsl:
				if feat in res:
					res[feat] += 1
				else:
					res[feat] = 1
		elif not len(ats) == 0:
			artist = ats[:-1] if ats[-1] == " " else ats
			artist = artist.replace("ryo","supercell").replace("ゴジマジP","ラマーズP").replace("ピノキオP","ピノキオピー").replace("ちょむP","TakeponG (ちょむP)").replace("1640mP","40mP").replace("じん (自然の敵P)","じん").replace("wowaka (現実逃避P)","wowaka").replace("マチゲリータP","マチゲリータ")
			if artist in res2:
				res2[artist] += 1
			else:
				res2[artist] = 1
sigma = [sum(res.values()),sum(res2.values())]
res = sorted(res.items(), key = lambda x : x[1], reverse = True)
res2 = sorted(res2.items(), key = lambda x : x[1], reverse = True)
songs = sorted(songs.items(), key = lambda x : x[1], reverse = True)
print("")
print("歌手別ランキング")
for result in res:
	print(result,"  ",result[1] / sigma[0] * 100,"%")
print("")
print("アーティスト名ランキング")
for result2 in res2:
	print(result2,"  ",result2[1] / sigma[1] * 100,"%")
print("")
print("曲別ランキング")
for song in songs:
	print(song)
input()
		

結果

出力にファイル数×0.014秒くらいかかる(以下の量だと37秒くらい)

TPE1タグの表記形式のばらつきの関係上、歌手別ランキングの数値/種類の値が%と一致しないので注意
Feat.以降がないファイル(特にSupernova,STARDOMシリーズ)は特定不可能なため


曲数:  2647
種類:  2074

歌手別ランキング
('初音ミク', 1286)    53.49417637271215 %
('鏡音リン', 275)    11.43926788685524 %
('巡音ルカ', 177)    7.362728785357738 %
('GUMI', 168)    6.988352745424292 %
('鏡音レン', 101)    4.201331114808652 %
('KAITO', 86)    3.577371048252912 %
('レン', 83)    3.4525790349417633 %
('MEIKO', 66)    2.7454242928452577 %
('神威がくぽ', 33)    1.3727121464226288 %
('IA', 27)    1.123128119800333 %
('MAYU', 20)    0.8319467554076538 %
('重音テト', 17)    0.7071547420965058 %
('結月ゆかり', 7)    0.2911813643926789 %
...

アーティスト名ランキング
('OSTER project', 97)    3.664525878352852 %
('DECO*27', 95)    3.5889686437476387 %
('40mP', 82)    3.0978466188137515 %
('doriko', 76)    2.871174914998111 %
('livetune', 76)    2.871174914998111 %
('TunEdge Music-ASCAP And Or Slynth Music-BMI', 74)    2.7956176803928976 %
('デッドボールP', 55)    2.07782395164337 %
('蝶々P', 55)    2.07782395164337 %
('supercell', 47)    1.775595013222516 %
('ナユタン星人', 44)    1.662259161314696 %
('Dios / シグナルP', 43)    1.6244805440120893 %
('黒うさP', 38)    1.4355874574990555 %
('トラボルタ', 34)    1.2844729882886285 %
('cosMo@暴走P', 33)    1.2466943709860219 %
('mothy_悪ノP', 33)    1.2466943709860219 %
('ピノキオピー', 31)    1.1711371363808085 %
('Last Note.', 30)    1.1333585190782016 %
('otetsu', 30)    1.1333585190782016 %
('ゆうゆ', 30)    1.1333585190782016 %
('八王子P', 30)    1.1333585190782016 %
('HMOとかの中の人。', 29)    1.095579901775595 %
('daniwellP', 26)    0.9822440498677748 %
('さつき が てんこもり', 26)    0.9822440498677748 %
('ひとしずく x やま△', 26)    0.9822440498677748 %
('みきとP', 25)    0.9444654325651681 %
('Nem', 24)    0.9066868152625615 %
('ラマーズP', 24)    0.9066868152625615 %
('KEMU VOXX', 23)    0.8689081979599547 %
('sasakure.UK', 23)    0.8689081979599547 %
('うたたP', 22)    0.831129580657348 %
...

曲別ランキング
('[Secret Track]', 10)
('ロミオとシンデレラ', 10)
('ココロ', 10)
('右肩の蝶', 9)
('千本桜', 9)
('トリノコシティ', 8)
('悪ノ召使', 8)
('-ハロー、プラネット。', 8)
('ルカルカ★ナイトフィーバー', 8)
('リンリンシグナル', 7)
('メルト', 7)
('炉心融解', 7)
('メランコリック', 7)
('いろは唄', 7)
('Yellow', 7)
('初音ミクの消失', 6)
('初音ミクの激唱', 6)
('サンドリヨン', 6)
('Packaged', 6)
('サイハテ', 6)
('恋スルVOC@LOID', 6)
('ローリンガール', 6)
('ぽっぴっぽー', 6)
('えれくとりっく・えんじぇぅ', 6)
('ダブルラリアット', 6)
('タイムマシン', 5)
('妄想スケッチ', 5)
('二息歩行', 5)
('歌に形はないけれど', 5)
('Weekender Girl', 5)
('悪ノ娘', 5)
('on the rocks', 5)
('マージナル', 5)
('初めての恋が終わる時', 5)
('ネトゲ廃人シュプレヒコール', 5)
('カンタレラ', 5)
('パラジクロロベンゼン', 5)
('Just Be Friends', 5)
('FREELY TOMORROW', 5)
('結ンデ開イテ羅刹ト骸', 5)
('from Y to Y', 5)
('ハジメテノオト', 5)
('こっち向いて Baby', 5)
('カラフル x メロディ', 5)
('愛言葉', 4)
('ゆめゆめ', 4)
('モノクロアクト', 4)
('インビジブル', 4)
('Tell Your World', 4)
('RIP=RELEASE', 4)
('PIANO-GIRL', 4)
('恋色病棟', 4)
('ミラクルペイント', 4)
('星屑ユートピア', 4)
('ワールドイズマイン', 4)
('Starduster', 4)
('カゲロウデイズ', 4)
('深海少女', 4)
('上弦の月', 4)
('1925', 4)
('No Logic', 4)
('腐れ外道とチョコレゐト', 4)
('ハッピーシンセサイザ', 4)
('マトリョシカ', 4)
('いーあるふぁんくらぶ', 4)
('私の時間', 4)
('ワールズエンド・ダンスホール', 4)
('え- あぁ、そう。', 4)
('Fire◎Flower', 4)
('シリョクケンサ', 3)
('ドレミファロンド', 3)
('夢地図', 3)
('キリトリセン', 3)
...