-
Notifications
You must be signed in to change notification settings - Fork 0
/
unoriginator.py
executable file
·165 lines (139 loc) · 5.43 KB
/
unoriginator.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
156
157
158
159
160
161
162
163
164
165
#!/usr/bin/env python3
import xml.sax
import re
import urllib.parse
import os
import argparse
import mutagen
from mutagen import MutagenError
from mutagen.id3 import ID3, TIT2
ENDS_WITH_ORIGINAL_MIX = re.compile(r'^(.*?)\s*(?:- original(?: mix)?|\(original(?: mix)?\))$', re.I)
total_seen_files = 0
total_updated_files = 0
total_itunes_entries = 0
didnt_process = []
dry_run = False
class iTunesHandler(xml.sax.ContentHandler):
"""
Stream parser for the iTunes XML file. Looks for music in the library which ends with some variation of
"Original Mix" and then calls update_tag() to remove the text from the file metadata.
"""
def __init__(self):
self.depth = 0
self.currentTag = ""
self.inTrack = False
self.nextStringIsName = False
self.nextStringIsLocation = False
self.trackName = ""
self.trackLocation = ""
# Call when an element starts
def startElement(self, tag, attributes):
self.depth += 1
self.currentTag = tag
if tag == 'dict' and self.depth == 4:
self.inTrack = True
# Call when an elements ends
def endElement(self, tag):
global total_itunes_entries
self.depth -= 1
self.currentTag = ""
if self.inTrack:
if tag == 'dict':
if ENDS_WITH_ORIGINAL_MIX.match(self.trackName):
if self.trackLocation[0:8] == 'file:///':
filename = urllib.parse.unquote(self.trackLocation)[7:]
update_tag(filename, dry_run=dry_run)
self.trackName = ""
self.trackLocation = ""
self.inTrack = False
total_itunes_entries += 1
elif tag == 'string':
self.nextStringIsName = False
self.nextStringIsLocation = False
# Call when a character is read
def characters(self, content):
if self.inTrack:
if self.currentTag == "key":
if content == 'Name':
self.nextStringIsName = True
elif content == 'Location':
self.nextStringIsLocation = True
elif self.currentTag == 'string':
if self.nextStringIsName:
self.trackName += content
elif self.nextStringIsLocation:
self.trackLocation += content
def update_tag(filename, dry_run=False):
"""
Gives a filename, tries to read the file metadata using Mutagen. If it ends with some variation of (Original Mix),
removed the text and saves the metadata. (Some tracks in the metadata may have already had the text removed if
iTunes hasn't rescanned the library - these are skipped).
:param filename:
:param dry_run:
:return:
"""
global total_updated_files
global total_seen_files
global didnt_process
total_seen_files += 1
try:
print("** Checking %s" % filename)
# id3 = ID3(filename)
# print("++ ID3 version: {}".format(id3.version))
f = mutagen.File(filename)
metadata_title = f.get('TIT2')
if metadata_title is None:
print(".. No TIT2 in file.")
didnt_process.append(filename)
return
print("++ Found %s" % metadata_title.text[0])
m = ENDS_WITH_ORIGINAL_MIX.match(metadata_title.text[0])
if m is not None:
fixed_metadata_title = m[1]
if metadata_title == fixed_metadata_title:
print(".. Metadata title '%s' already looks fine to me." % metadata_title)
return
f.tags.add(TIT2(text=[fixed_metadata_title]))
if not dry_run:
f.save()
total_updated_files += 1
return
else:
print(".. Did mot match (original mix) for {}".format(filename))
except MutagenError as e:
print("!! Could not read file: {}".format(e))
except AttributeError:
print('!! No tags found: {}'.format(e))
didnt_process.append(filename)
if __name__ == "__main__":
default_path = os.path.join(os.getenv("HOME"), "Music/iTunes/iTunes Library.xml")
parser = argparse.ArgumentParser()
parser.add_argument("--dry-run", help="don't actually update the metadata, just say what would have been updated",
action="store_true")
parser.add_argument("itunes_xml_file",
help="Filename of the iTunes Music library. Defaults to {}".format(default_path),
default=default_path, nargs='?')
args = parser.parse_args()
dry_run = args.dry_run
if dry_run:
print("** DRY RUN ONLY **")
# create an XMLReader
parser = xml.sax.make_parser()
# turn off namepsaces
parser.setFeature(xml.sax.handler.feature_namespaces, 0)
# override the default ContextHandler
Handler = iTunesHandler()
parser.setContentHandler(Handler)
try:
parser.parse(args.itunes_xml_file)
except ValueError:
print("Error reading file. Is that the right iTunes Library.xml file?")
exit(-1)
print()
print("Total iTunes entries parsed: {}".format(total_itunes_entries))
print("Total candidate files to fix: {}".format(total_seen_files))
print("Total files actually fixed: {}".format(total_updated_files))
if len(didnt_process) > 0:
print("Didn't process the following files:")
for i in didnt_process:
print("\t{}".format(i))