import os import urllib.parse import xml.etree.ElementTree import dateutil.parser class XmlDict: def __init__(self, node): assert isinstance(node, xml.etree.ElementTree.Element), node assert node.tag == 'dict', node.tag self._node = node def _get_value_node(self, key): # WORKAROUND method getnext() is sadly not available for child_idx, child_node in enumerate(self._node): if child_node.tag == 'key' and child_node.text == key: return self._node[child_idx + 1] raise KeyError(key) @staticmethod def _parse_value_node(value_node): if value_node.tag == 'string': return value_node.text if value_node.tag == 'integer': return int(value_node.text) if value_node.tag == 'date': return dateutil.parser.parse(value_node.text) if value_node.tag == 'dict': return XmlDict(value_node) raise ValueError(value_node.tag) def __getitem__(self, key): value_node = self._get_value_node(key) return XmlDict._parse_value_node(value_node) def get(self, key, default): try: return self[key] except KeyError: return default def items(self): for key_node, value_node in zip(*[iter(self._node)] * 2): assert key_node.tag == 'key' key = key_node.text value = XmlDict._parse_value_node(value_node) yield (key, value) def values(self): for _, value in self.items(): yield value class Track: LOCAL_LOCATION_URL_PREFIX = 'file://localhost/' def __init__(self, track_dict): assert isinstance(track_dict, XmlDict) self._dict = track_dict self._id = self._dict['Track ID'] assert isinstance(self._id, int) self._location_url = self._dict.get('Location', None) self._play_count = self._dict.get('Play Count', 0) assert isinstance(self._play_count, int) self._last_play_dt = self._dict.get('Play Date UTC', None) @property def track_id(self): return self._id @property def location_url(self): return self._location_url @property def local(self): return self.location_url is not None \ and self.location_url.startswith(self.LOCAL_LOCATION_URL_PREFIX) @property def local_path(self): if self.local: return os.path.sep + urllib.parse.unquote( self.location_url[len(self.LOCAL_LOCATION_URL_PREFIX):], ) raise ValueError(self.location_url) @property def play_count(self): return self._play_count @property def last_play_dt(self): return self._last_play_dt class XmlLibrary: def __init__(self, path): self._tree = xml.etree.ElementTree.parse(path) self._root_dict = XmlDict(self._tree.find('./dict')) self._id = self._root_dict['Library Persistent ID'] assert isinstance(self._id, str), self._id assert len(self._id) > 4 @property def library_id(self): return self._id @property def tracks(self): for track_dict in self._root_dict['Tracks'].values(): yield Track(track_dict)