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)