From 426ad381de39667ed3e168fdbe2193adbb87de00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A8=D1=83=D1=80=D1=83=D0=BF=D0=BE=D0=B2=20=D0=98=D0=BB?= =?UTF-8?q?=D1=8C=D1=8F=20=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B8=D1=87?= Date: Sat, 7 Feb 2026 11:24:59 +0300 Subject: [PATCH] tmp --- Flow.py | 13 ++ main.py | 7 +- src/Library.py | 17 ++ src/backends/itunes/ParseItunesToJson.py | 101 ------------ src/backends/itunes/ParseItunesXml.py | 192 +++++++++++++++++++++++ 5 files changed, 226 insertions(+), 104 deletions(-) delete mode 100755 src/backends/itunes/ParseItunesToJson.py create mode 100755 src/backends/itunes/ParseItunesXml.py diff --git a/Flow.py b/Flow.py index 5e813e0..0031017 100644 --- a/Flow.py +++ b/Flow.py @@ -13,6 +13,8 @@ from src.backends.spotify.ParseSpotify import Parser from src.backends.local.LocalLibraryIndexer import update_local_library from src.backends.local.ParseLocal import LocalParser +from src.backends.itunes.ParseItunesXml import ParseItunesXml + from src.mappers.RemoteLibraryResolver import resolve_remote_tracks from src.StatGenerator import log_stats @@ -43,6 +45,17 @@ class Flow: LibrarySaver(library_parsed).save(work_dir / "spotify_library.json") + @staticmethod + def parse_itunes_library(): + parser = ParseItunesXml() + + library_parsed = parser.parse( + work_dir / "itunes" / "Library.xml", + work_dir / "itunes" / "Library.json" + ) + + LibrarySaver(library_parsed).save(work_dir / "itunes_library.json") + @staticmethod def parse_local_library(): parser = LocalParser() diff --git a/main.py b/main.py index 646539a..26588d1 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,6 @@ - - import Flow + def test1(): flow = Flow.Flow() @@ -14,13 +13,15 @@ def test1(): # flow.fetch_and_save_lyrics() + def main(): flow = Flow.Flow() # flow.fetch_spotify() - flow.fetch_local() + # flow.fetch_local() flow.parse_spotify_library() + flow.parse_itunes_library() flow.parse_local_library() flow.load_libraries() diff --git a/src/Library.py b/src/Library.py index 46d7566..7bcefac 100644 --- a/src/Library.py +++ b/src/Library.py @@ -4,6 +4,23 @@ from datetime import datetime from pathlib import Path from typing import List, Optional, Any +class Entity: + def __init__(self): + self.id: str = None + self.mbid : str = None + + self.title: str = None + + self.fav: bool = False + self.rating: int = 0 + self.play_count : int = 0 + self.date_added : datetime = None + self.date_released : datetime = None + self.date_last_play : datetime = None + self.auto_score: float = 0 + + self.local_id: str = None + self.resolved_percentage : float = 0 class Artist: def __init__(self): diff --git a/src/backends/itunes/ParseItunesToJson.py b/src/backends/itunes/ParseItunesToJson.py deleted file mode 100755 index 1505a36..0000000 --- a/src/backends/itunes/ParseItunesToJson.py +++ /dev/null @@ -1,101 +0,0 @@ -import xmltodict -import json - - -def flatten_dict(d): - - out = [] - for song in d: - newSong = {} - - strCount = 0 - intCount = 0 - dateCount = 0 - - def getStr(id): - nonlocal strCount - if id not in song["key"]: - return "undef" - if len(song["string"]) <= strCount: - return "error" - strCount += 1 - return song["string"][strCount - 1] - - def getInt(id): - nonlocal intCount - if id not in song["key"]: - return -1 - if len(song["integer"]) <= intCount: - return -1 - intCount += 1 - return song["integer"][intCount - 1] - - def getDate(id): - nonlocal dateCount - if id not in song["key"]: - return "undef" - if len(song["date"]) <= dateCount: - return "error" - dateCount += 1 - return song["date"][dateCount - 1] - - newSong["Track ID"] = getInt("Track ID") - newSong["Name"] = getStr("Name") - newSong["Artist"] = getStr("Artist") - newSong["Album Artist"] = getStr("Album Artist") - newSong["Composer"] = getStr("Composer") - newSong["Album"] = getStr("Album") - newSong["Genre"] = getStr("Genre") - newSong["Kind"] = getStr("Kind") - newSong["Size"] = getInt("Size") - newSong["Total Time"] = getInt("Total Time") - newSong["Disc Number"] = getInt("Disc Number") - newSong["Disc Count"] = getInt("Disc Count") - newSong["Track Number"] = getInt("Track Number") - newSong["Track Count"] = getInt("Track Count") - newSong["Year"] = getInt("Year") - newSong["Date Modified"] = getDate("Date Modified") - newSong["Date Added"] = getDate("Date Added") - newSong["Bit Rate"] = getInt("Bit Rate") - newSong["Sample Rate"] = getInt("Sample Rate") - newSong["Play Count"] = getInt("Play Count") - newSong["Play Date"] = getInt("Play Date") - newSong["Play Date UTC"] = getDate("Play Date UTC") - newSong["Skip Count"] = getInt("Skip Count") - newSong["Skip Date"] = getDate("Skip Date") - newSong["Release Date"] = getDate("Release Date") - newSong["Album Rating"] = getInt("Album Rating") - newSong["Album Rating Computed"] = "Album Rating Computed" in song["key"] - newSong["Loved"] = "Loved" in song["key"] - newSong["Album Loved"] = "Album Loved" in song["key"] - newSong["Explicit"] = "Explicit" in song["key"] - newSong["Compilation"] = "Compilation" in song["key"] - newSong["Artwork Count"] = getInt("Artwork Count") - newSong["Sort Album"] = getStr("Sort Album") - newSong["Sort Artist"] = getStr("Sort Artist") - newSong["Sort Name"] = getStr("Sort Name") - newSong["Persistent ID"] = getStr("Persistent ID") - newSong["Track Type"] = getStr("Track Type") - - out.append(newSong) - - return out - - -def convert(filename, out_path): - with open(filename, 'r', encoding='utf-8') as xml_file: - data_dict = xmltodict.parse(xml_file.read()) - - # Extract the "Tracks" dictionary to be flattened - tracks_dict = data_dict['plist']['dict']['dict'] - - flat_tracks_dict = flatten_dict(tracks_dict["dict"]) - - json_data = json.dumps(flat_tracks_dict, indent=2) - - with open(out_path, 'w', encoding='utf-8') as json_file: - json_file.write(json_data) - - -if __name__ == "__main__": - convert('./prefetched/ItunesLibrary.xml', './prefetched/ItunesLibrary.json') diff --git a/src/backends/itunes/ParseItunesXml.py b/src/backends/itunes/ParseItunesXml.py new file mode 100755 index 0000000..0af465f --- /dev/null +++ b/src/backends/itunes/ParseItunesXml.py @@ -0,0 +1,192 @@ +import xmltodict +import json +import xmltodict +import json +import re + +from src.Library import * + +class XmlToJson: + def flatten_dict(self, d): + + out = [] + for song in d: + newSong = {} + + strCount = 0 + intCount = 0 + dateCount = 0 + + def getStr(id): + nonlocal strCount + if id not in song["key"]: + return "undef" + if len(song["string"]) <= strCount: + return "error" + strCount += 1 + return song["string"][strCount - 1] + + def getInt(id): + nonlocal intCount + if id not in song["key"]: + return -1 + if len(song["integer"]) <= intCount: + return -1 + intCount += 1 + return song["integer"][intCount - 1] + + def getDate(id): + nonlocal dateCount + if id not in song["key"]: + return "undef" + if len(song["date"]) <= dateCount: + return "error" + dateCount += 1 + return song["date"][dateCount - 1] + + newSong["Track ID"] = getInt("Track ID") + newSong["Name"] = getStr("Name") + newSong["Artist"] = getStr("Artist") + newSong["Album Artist"] = getStr("Album Artist") + newSong["Composer"] = getStr("Composer") + newSong["Album"] = getStr("Album") + newSong["Genre"] = getStr("Genre") + newSong["Kind"] = getStr("Kind") + newSong["Size"] = getInt("Size") + newSong["Total Time"] = getInt("Total Time") + newSong["Disc Number"] = getInt("Disc Number") + newSong["Disc Count"] = getInt("Disc Count") + newSong["Track Number"] = getInt("Track Number") + newSong["Track Count"] = getInt("Track Count") + newSong["Year"] = getInt("Year") + newSong["Date Modified"] = getDate("Date Modified") + newSong["Date Added"] = getDate("Date Added") + newSong["Bit Rate"] = getInt("Bit Rate") + newSong["Sample Rate"] = getInt("Sample Rate") + newSong["Play Count"] = getInt("Play Count") + newSong["Play Date"] = getInt("Play Date") + newSong["Play Date UTC"] = getDate("Play Date UTC") + newSong["Skip Count"] = getInt("Skip Count") + newSong["Skip Date"] = getDate("Skip Date") + newSong["Release Date"] = getDate("Release Date") + newSong["Album Rating"] = getInt("Album Rating") + newSong["Album Rating Computed"] = "Album Rating Computed" in song["key"] + newSong["Loved"] = "Loved" in song["key"] + newSong["Album Loved"] = "Album Loved" in song["key"] + newSong["Explicit"] = "Explicit" in song["key"] + newSong["Compilation"] = "Compilation" in song["key"] + newSong["Artwork Count"] = getInt("Artwork Count") + newSong["Sort Album"] = getStr("Sort Album") + newSong["Sort Artist"] = getStr("Sort Artist") + newSong["Sort Name"] = getStr("Sort Name") + newSong["Persistent ID"] = getStr("Persistent ID") + newSong["Track Type"] = getStr("Track Type") + + out.append(newSong) + + return out + + + def convert(self, filename, out_path): + with open(filename, 'r', encoding='utf-8') as xml_file: + data_dict = xmltodict.parse(xml_file.read()) + + # Extract the "Tracks" dictionary to be flattened + tracks_dict = data_dict['plist']['dict']['dict'] + + flat_tracks_dict = self.flatten_dict(tracks_dict["dict"]) + + json_data = json.dumps(flat_tracks_dict, indent=2) + + with open(out_path, 'w', encoding='utf-8') as json_file: + json_file.write(json_data) + + +class ParseItunesXml: + def __init__(self): + self.track_map: dict[str, Track] = {} + self.artist_map: dict[str, Artist] = {} + self.album_map: dict[str, Album] = {} + self.library = Library() + + + def parse_artists(self, data: list) -> List[Artist]: + if not data: + raise "No artist found" + + artists = [] + + for artist_data in data: + name = artist_data + artist_id = name + + if artist_id in self.artist_map: + artists.append(self.artist_map[artist_id]) + continue + + artist = Artist() + artist.id = artist_id + artist.title = name + + self.artist_map[artist_id] = artist + self.library.artists.append(artist) + + artists.append(artist) + + return artists + + def parse_album(self, data: str) -> Album: + if not data: + raise "album is none" + + name = data + album_id = name + + if album_id in self.album_map: + return self.album_map[album_id] + + album = Album() + album.id = album_id + album.title = name + + self.album_map[album_id] = album + self.library.albums.append(album) + + return album + + + def parse(self, tracks_file_xml: Path, tracks_file_json: Path) -> Library: + XmlToJson().convert(tracks_file_xml, tracks_file_json) + + tracks_content = json.loads(tracks_file_json.read_text()) + + for data in tracks_content: + track = Track() + + track.id = data.get("Track ID") + track.title = data.get("Name") + track.artists = self.parse_artists([data.get("Artist")]) + track.album = self.parse_album(data.get("Album")) + + if not track.album or not len(track.artists) or not track.title: + raise "error parsing" + + self.track_map[track.id] = track + self.library.tracks.append(track) + + for track_id, track in self.track_map.items(): + for artist in track.artists: + if artist not in track.album.artists: + track.album.artists.append(artist) + + self.library.update_cache() + + self.library.name = "Itunes" + + return self.library + + + def parse_track_item(self, id, track: dict[str, Any]) -> Track: + + return track +