import re from ytmusicapi.type_alias import JsonDict, JsonList from ._utils import * def parse_song_artists(data: JsonDict, index: int) -> JsonList: flex_item = get_flex_column_item(data, index) if not flex_item: return [] else: runs = flex_item["text"]["runs"] return parse_song_artists_runs(runs) def parse_song_artists_runs(runs: JsonList) -> JsonList: artists = [] for j in range(int(len(runs) / 2) + 1): artists.append({"name": runs[j * 2]["text"], "id": nav(runs[j * 2], NAVIGATION_BROWSE_ID, True)}) return artists def parse_song_runs(runs: JsonList) -> JsonDict: parsed: JsonDict = {"artists": []} for i, run in enumerate(runs): if i % 2: # uneven items are always separators continue text = run["text"] if "navigationEndpoint" in run: # artist or album item = {"name": text, "id": nav(run, NAVIGATION_BROWSE_ID, True)} if item["id"] and (item["id"].startswith("MPRE") or "release_detail" in item["id"]): # album parsed["album"] = item else: # artist parsed["artists"].append(item) else: # note: YT uses non-breaking space \xa0 to separate number and magnitude if re.match(r"^\d([^ ])* [^ ]*$", text) and i > 0: parsed["views"] = text.split(" ")[0] elif re.match(r"^(\d+:)*\d+:\d+$", text): parsed["duration"] = text parsed["duration_seconds"] = parse_duration(text) elif re.match(r"^\d{4}$", text): parsed["year"] = text else: # artist without id parsed["artists"].append({"name": text, "id": None}) return parsed def parse_song_album(data: JsonDict, index: int) -> JsonDict | None: flex_item = get_flex_column_item(data, index) browse_id = nav(flex_item, TEXT_RUN + NAVIGATION_BROWSE_ID, True) return None if not flex_item else {"name": get_item_text(data, index), "id": browse_id} def parse_song_library_status(item: JsonDict) -> bool: """Returns True if song is in the library""" library_status = nav(item, [TOGGLE_MENU, "defaultIcon", "iconType"], True) return library_status == "LIBRARY_SAVED" def parse_song_menu_tokens(item: JsonDict) -> dict[str, str | None]: toggle_menu = item[TOGGLE_MENU] library_add_token = nav(toggle_menu, ["defaultServiceEndpoint", *FEEDBACK_TOKEN], True) library_remove_token = nav(toggle_menu, ["toggledServiceEndpoint", *FEEDBACK_TOKEN], True) in_library = parse_song_library_status(item) if in_library: library_add_token, library_remove_token = library_remove_token, library_add_token return {"add": library_add_token, "remove": library_remove_token} def parse_like_status(service: JsonDict) -> str: status = ["LIKE", "INDIFFERENT"] return status[status.index(service["likeEndpoint"]["status"]) - 1]