Last active 1693857166

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

Revision 1faf547bc5bf7705ba40f0e1ebfb4cfa00e99dfd

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: 19ms