Last active 1693857166

A little python script using mutagen module and MusicBrainz APIs to recover ID3 tags for a mp3 file

ID3Tagger.py Raw
1import argparse
2import datetime
3import json
4import urllib.request
5import urllib.parse
6import acoustid
7from mutagen.id3 import ID3NoHeaderError, ID3, TIT2, TALB, TPE2, TPE1, APIC, TRCK, TLEN, TDRC, TYER
8
9base_url = "https://musicbrainz.org/ws/2"
10cover_art_url = f"https://coverartarchive.org/release/"
11
12
13def milliseconds_to_duration(milliseconds):
14 seconds, milliseconds = divmod(milliseconds, 1000)
15 minutes, seconds = divmod(seconds, 60)
16 hours, minutes = divmod(minutes, 60)
17
18 music_duration = "{:02}:{:02}:{:02}.{}".format(hours, minutes, seconds, milliseconds)
19
20 return music_duration
21
22
23def get_key(data, key):
24 if key not in data or len(data[key]) <= 0:
25 return None
26 return data[key][0]
27
28
29def get_recording(data):
30 return get_key(data, 'recordings')
31
32
33def get_release(recording):
34 return get_key(recording, 'releases')
35
36
37def get_media(release):
38 return get_key(release, 'media')
39
40
41def concat_artists(data):
42 if 'artist-credit' not in data or len(data['artist-credit']) <= 0:
43 return None
44 artists = []
45 for artist in data['artist-credit']:
46 artists.append(artist['name'])
47 return ",".join(artists)
48
49
50def get_data_from_media(media, tags, title):
51 if media is None:
52 print("Found no media for this release, no additional data can be recovered")
53 return tags
54 if 'tracks' not in media or len(media['tracks']) <=0:
55 print("Found no track for this release, no additional data can be recovered")
56 return tags
57 for track in media['tracks']:
58 if track['title'] == title:
59 track_num = f"{track['number']}/{media['track-count']}"
60 print(f"TRCK={track_num}")
61 tags["TRCK"] = TRCK(encoding=3, text=track_num)
62 return tags
63
64
65def get_data_from_release(release, tags):
66 if release is None:
67 print("Found no release for this recording, no additional data can be recovered")
68 return tags
69 print(f"TALB={release['title']}")
70 tags["TALB"] = TALB(encoding=3, text=release['title'])
71 release_artists = concat_artists(release)
72 if release_artists is None:
73 print("Found no artist for this release")
74 else:
75 print(f"TPE2={release_artists}")
76 tags["TPE2"] = TPE2(encoding=3, text=release_artists)
77 try:
78 with urllib.request.urlopen(f"{cover_art_url}{release['id']}/front") as cover_art_response:
79 print(f"APIC={cover_art_url}{release['id']}/front")
80 tags["APIC"] = APIC(3, 'image/jpeg', 3, 'Front cover', cover_art_response.read())
81 except:
82 print("Found no cover art for this release")
83 return tags
84
85
86def get_data_from_recording(recording, tags):
87 if recording is None:
88 print("Got no match for this recording, exiting...")
89 exit(1)
90 print(f"TIT2={recording['title']}")
91 tags["TIT2"] = TIT2(encoding=3, text=recording['title'])
92 if 'length' in recording:
93 print(f"TLEN={milliseconds_to_duration(recording['length'])}")
94 tags["TLEN"] = TLEN(encoding=3, text=milliseconds_to_duration(recording['length']))
95 track_artists = concat_artists(recording)
96 if track_artists is None:
97 print("Found no artist for this track")
98 else:
99 print(f"TPE1={track_artists}")
100 tags["TPE1"] = TPE1(encoding=3, text=track_artists)
101 if 'first-release-date' not in recording:
102 print("Found no release date for this recording")
103 return tags
104 release_date: datetime.date = datetime.date.fromisoformat(recording['first-release-date'])
105 print(f"TDRC/TYER={release_date.year}")
106 tags["TYER"] = TYER(encoding=3, text=f"{release_date.year}")
107 tags["TDRC"] = TDRC(encoding=3, text=f"{release_date.year}")
108 return tags
109
110
111def main():
112 parser = argparse.ArgumentParser(
113 prog='metadataFetcher',
114 description='fetches metadata and writes it in a mp3 file'
115 )
116 parser.add_argument('api_key')
117 parser.add_argument('filename')
118 args = parser.parse_args()
119
120 matches = list(acoustid.match(args.api_key, args.filename))
121 if len(matches) <= 0:
122 print("Could not get a match for the fingerprint, exiting...")
123 exit(1)
124
125 try:
126 tags = ID3(args.filename)
127 except ID3NoHeaderError:
128 print("Adding ID3 header...")
129 tags = ID3()
130
131 with urllib.request.urlopen(f"{base_url}/recording/{matches[0][1]}?fmt=json&inc=releases+artists+media+artist-credits") as response:
132 body = response.read()
133 recording = json.loads(body)
134 tags = get_data_from_recording(recording, tags)
135
136 release = get_release(recording)
137 tags = get_data_from_release(release, tags)
138 if release is not None:
139 media = get_media(release)
140 get_data_from_media(media, tags, recording['title'])
141
142 tags.save()
143
144
145if __name__ == '__main__':
146 main()

Powered by Opengist ⋅ Load: 21ms