Last active 1693857166

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

Revision 237499c4f40e4bbbe1627cfc066fe39107f80bae

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

Powered by Opengist ⋅ Load: 10ms