[Python]持っている音楽ファイルのアーティスト割合などを取得する

# はじめに
- ライブラリだと文字化けするので使わずに作成
- 持っている音楽のアーティスト、歌手、曲の重複数をコマンドプロンプトに表示
- 音楽ジャンルはVOCALOIDを想定していますが、他ジャンルが入っていても動きます

# 環境
- Windows11
- Visual Studio 2022 (Community)
- Python 3.9(64bit)

# VOCALOIDジャンル特有のファイル構造(他ジャンルでも同じ?)
- mp3タグの文字コードがバラバラ(タグを解析するライブラリを使うと文字化けする)
- アーティスト名に別名が存在する人が多い(例:ラマーズPとゴジマジP)　また、表記ゆれもある
- アーティスト名の表記形式は、○○ Feat.△△　(だいたいこれ)　○○ loves △△(デッドボールPのアルバム)　の2種類が存在
- 名前の区切りは、　X,Y(だいたいこれ)　X+Y　X・Y　X&Y　X、Y(一部のアルバム)

# 変数の準備など

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

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

# mp3ファイルを取得

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

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

~~~python:Python
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タグを取得するライブラリは存在するが、文字化けするので使用しない。
今のところ、文字コードは　UTF-8,Shift-JIS,UTF-16のいずれかで対応できたので、try-exceptを使いしらみつぶしに変換する
ヌル文字はここで削除する(utf-16を除く)
~~~python:Python
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.初音ミク,鏡音リン)
鏡音リン・レンを鏡音リン・鏡音レンに直す(後でランキングをつけるため)
~~~python:Python
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()
~~~

# 完成形

~~~python:Python
import os
# -*- coding: shift-jis -*-
dir_path = r"ミュージックフォルダまでのパス"
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()
~~~

# 結果
~~~
曲数:  2937
種類:  2275

歌手別ランキング
('初音ミク', 1359)    52.5725338491296 %
('鏡音リン', 294)    11.37330754352031 %
('鏡音レン', 200)    7.7369439071566735 %
...(略)...
アーティスト名ランキング
('DECO*27', 119)    4.051753489955737 %
('OSTER project', 97)    3.302689819543752 %
('40mP', 83)    2.826012938372489 %
...(略)...
曲別ランキング
('[Secret Track]', 10)
('ロミオとシンデレラ', 10)
('ココロ', 10)
...(略)...
~~~