-
Notifications
You must be signed in to change notification settings - Fork 2
/
get_mld_metadata.py
155 lines (136 loc) · 5.89 KB
/
get_mld_metadata.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# -*- coding: utf-8 -*-
import os
import sys
import pprint
import datetime
import re
def get_metadata(mld: bytes):
metadata = {}
# header part
data_info_size = int.from_bytes(mld[0x08:0x0A]) - 3
metadata["file_byte"] = int.from_bytes(mld[0x04:0x08]) + 8
metadata["data_type_major"] = mld[0x0A]
metadata["data_type_minor"] = mld[0x0B]
metadata["number_of_tracks"] = mld[0x0C]
# data infomation part
data_info = mld[0x0D:0x0D+data_info_size]
data_info_i = 0
while data_info_i < data_info_size:
magic = data_info[data_info_i:data_info_i+4].decode("shift_jis", errors="ignore")
size = int.from_bytes(data_info[data_info_i+4:data_info_i+6])
data = data_info[data_info_i+6:data_info_i+6+size]
match magic:
case "titl":
metadata["title"] = data.decode("shift_jis", errors="ignore")
case "sorc":
metadata["from"] = convert_to_bin(data)[0:7]
metadata["protect"] = bool(int(convert_to_bin(data)[7]))
case "vers":
metadata["version"] = data.decode("shift_jis", errors="ignore")
case "date":
date = data.decode("shift_jis", errors="ignore")
try:
metadata["date"] = datetime.datetime.strptime(date, '%Y%m%d')
except ValueError:
pass
case "copy":
metadata["copyright"] = data.decode("shift_jis", errors="ignore")
# case "prot":
# metadata["prot"] = data.decode("shift_jis", errors="ignore")
case "note":
metadata["note_length"] = int.from_bytes(data)
case "exst":
metadata["extended_status_dependent_length"] = int.from_bytes(data)
case "supt":
metadata["generator"] = data.decode("shift_jis", errors="ignore")
# case "auth":
# metadata["auth"] = int.from_bytes(data)
# case "ainf":
# metadata["ainf"] = int.from_bytes(data)
# case "thed":
# metadata["thed"] = int.from_bytes(data)
case _:
if not ("unknown_magic" in metadata): metadata["unknown_magic"] = []
metadata["unknown_magic"].append({
"magic": magic,
"int": int.from_bytes(data),
"str": data.decode('shift_jis', errors='ignore')
})
data_info_i += 6 + size
return metadata
def convert_to_bin(bytes) -> str:
bin_list = list()
for byte in bytes:
bin_list.append(format(byte, '08b'))
return ''.join(map(str, bin_list))
def is_valid_mld(mld: bytes):
if mld[0:4] != b"melo": return False
if len(mld) < (0x0C): return False
data_info_size = int.from_bytes(mld[0x08:0x0A]) - 3
if len(mld) < (0x0D+data_info_size): return False
file_byte = int.from_bytes(mld[0x04:0x08]) + 8
# Maximum Size 3MB
if file_byte > (3 * 1000 * 1000): return False
data_info = mld[0x0D:0x0D+data_info_size]
if not re.match(r"titl|sorc|vers|date|copy|prot|note|exst|supt|auth|ainf|thed",
data_info[0:4].decode("shift_jis", errors="ignore")):
return False
data_info_i = 0
while data_info_i < data_info_size:
magic = data_info[data_info_i:data_info_i+4].decode("shift_jis", errors="ignore")
size = int.from_bytes(data_info[data_info_i+4:data_info_i+6])
data = data_info[data_info_i+6:data_info_i+6+size]
match magic:
case "sorc":
if size != 1: return False
case "vers":
if size != 4: return False
case "date":
if size != 8: return False
data_info_i += 6 + size
return True
def main(file_path):
with open(file_path, "rb") as file:
mld_binary = file.read()
if not is_valid_mld(mld_binary):
print("It's not a mld.")
return
metadata = get_metadata(mld_binary)
# format
match metadata["data_type_major"]:
case 1:
metadata["data_type_major"] = f"{metadata['data_type_major']} (ringtone)"
case 2:
metadata["data_type_major"] = f"{metadata['data_type_major']} (music)"
case _:
metadata["data_type_major"] = f"{metadata['data_type_major']} (unknown)"
match metadata["data_type_minor"]:
case 0:
metadata["data_type_minor"] = f"{metadata['data_type_minor']} (music)"
case 1:
metadata["data_type_minor"] = f"{metadata['data_type_minor']} (all)"
case 2:
metadata["data_type_minor"] = f"{metadata['data_type_minor']} (part)"
case _:
metadata["data_type_minor"] = f"{metadata['data_type_minor']} (unknown)"
metadata["number_of_tracks"] = f"{metadata['number_of_tracks']} ({metadata['number_of_tracks']*4} chords)"
if ("from" in metadata):
match metadata["from"]:
case "0000000":
metadata["from"] += " (download from network)"
case "0000001":
metadata["from"] += " (using terminal input function)"
case "0000010":
metadata["from"] += " (input using terminal external I/F)"
case _:
metadata["from"] += " (unknown: {metadata['from']})"
if ("version" in metadata):
integer_part = int(metadata["version"][0:2])
decimal_part = int(metadata["version"][2:4])
metadata["version"] += f" (MFi{integer_part}.{decimal_part:02})"
if ("date" in metadata):
metadata["date"] = metadata["date"].strftime('%Y/%m/%d')
pprint.pprint(metadata)
if __name__ == "__main__":
for file_path in sys.argv[1:]:
main(file_path)