1
0
mirror of https://github.com/natekspencer/hacs-oasis_mini.git synced 2025-11-08 05:03:52 -05:00

24 Commits

Author SHA1 Message Date
Nathan Spencer
0cab687cef Merge pull request #87 from natekspencer/error-translations
Add error translations
2025-08-02 08:23:18 -06:00
Nathan Spencer
581f41c517 Add error translations 2025-08-02 14:21:34 +00:00
Nathan Spencer
7705d61a4f Merge pull request #86 from natekspencer/status-icons
Update status icons for busy and sleeping
2025-08-02 07:55:38 -06:00
Nathan Spencer
3a8e274d26 Update status icons for busy and sleeping 2025-08-02 13:54:35 +00:00
Nathan Spencer
6c6ce70932 Merge pull request #85 from natekspencer/cloud-playlists
Add cloud playlists
2025-08-02 07:52:24 -06:00
Nathan Spencer
8a72aba294 Add cloud playlists 2025-08-02 13:48:58 +00:00
Nathan Spencer
9949241c84 Merge pull request #83 from natekspencer/natekspencer-patch-1
Change schedule for update-tracks workflow
2025-07-24 13:38:59 -06:00
Nathan Spencer
b07fc68b21 Change schedule for update-tracks workflow 2025-07-24 13:37:49 -06:00
Nathan Spencer
91d03f11a8 Merge pull request #82 from natekspencer/update-tracks
Update tracks
2025-07-24 13:35:53 -06:00
natekspencer
4d2c7a0199 Update tracks 2025-07-24 19:20:41 +00:00
Nathan Spencer
7c650949d8 Merge pull request #81 from natekspencer/update-tracks
Fix track info with new format
2025-07-23 13:52:47 -06:00
Nathan Spencer
2d37fb691f Fix track info with new format 2025-07-23 19:49:46 +00:00
Nathan Spencer
21fd8a63ba Merge pull request #80 from natekspencer/led-effects
Add additional led effects
2025-07-22 18:09:16 -06:00
Nathan Spencer
552339665f Add additional led effects 2025-07-23 00:06:10 +00:00
Nathan Spencer
85449a5363 Merge pull request #79 from natekspencer/add-sleep-button
Add sleep button
2025-07-22 17:37:52 -06:00
Nathan Spencer
d2bc89bdd7 Add sleep button 2025-07-22 23:36:33 +00:00
Nathan Spencer
06008e8f4c Merge pull request #78 from natekspencer/firmware-2.02-temp-fix
Add fix for firmware 2.02 led issue
2025-07-22 17:34:36 -06:00
Nathan Spencer
9fdfd8129f Merge pull request #76 from natekspencer/update-tracks
Update tracks
2025-07-22 17:31:03 -06:00
natekspencer
f9237927d9 Update tracks 2025-07-22 19:20:38 +00:00
Nathan Spencer
dcd8db52f5 Merge pull request #75 from natekspencer/update-tracks
Update tracks
2025-07-21 13:21:11 -06:00
natekspencer
86cf060af0 Update tracks 2025-07-21 19:20:16 +00:00
Nathan Spencer
d7a803abc7 Merge pull request #74 from natekspencer/update-tracks
Update tracks
2025-07-21 09:20:31 -06:00
natekspencer
a1bb4c78fb Update tracks 2025-07-18 19:19:31 +00:00
Nathan Spencer
50f7b270f2 Add temp fix for firmware 2.02 led issue 2025-03-26 17:40:26 +00:00
15 changed files with 11312 additions and 4341 deletions

View File

@@ -1,7 +1,7 @@
name: Update tracks
on:
schedule:
- cron: "0 19 * * *"
- cron: "0 19 * * 1"
permissions:
contents: write
pull-requests: write

View File

@@ -3,12 +3,14 @@
from __future__ import annotations
import logging
from typing import Any
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
import homeassistant.helpers.device_registry as dr
import homeassistant.helpers.entity_registry as er
from .const import DOMAIN
from .coordinator import OasisMiniCoordinator
@@ -89,3 +91,33 @@ async def async_remove_entry(hass: HomeAssistant, entry: OasisMiniConfigEntry) -
async def update_listener(hass: HomeAssistant, entry: OasisMiniConfigEntry) -> None:
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Migrate old entry."""
_LOGGER.debug(
"Migrating configuration from version %s.%s", entry.version, entry.minor_version
)
if entry.version == 1 and entry.minor_version == 1:
# Need to update previous playlist select entity to queue
@callback
def migrate_unique_id(entity_entry: er.RegistryEntry) -> dict[str, Any] | None:
"""Migrate the playlist unique ID to queue."""
if entity_entry.domain == "select" and entity_entry.unique_id.endswith(
"-playlist"
):
unique_id = entity_entry.unique_id.replace("-playlist", "-queue")
return {"new_unique_id": unique_id}
return None
await er.async_migrate_entries(hass, entry.entry_id, migrate_unique_id)
hass.config_entries.async_update_entry(entry, minor_version=2, version=1)
_LOGGER.debug(
"Migration to configuration version %s.%s successful",
entry.version,
entry.minor_version,
)
return True

View File

@@ -61,6 +61,11 @@ DESCRIPTORS = (
translation_key="random_track",
press_fn=play_random_track,
),
OasisMiniButtonEntityDescription(
key="sleep",
translation_key="sleep",
press_fn=lambda device: device.async_sleep(),
),
)

View File

@@ -62,6 +62,7 @@ class OasisMiniConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Oasis Mini."""
VERSION = 1
MINOR_VERSION = 2
@staticmethod
@callback

View File

@@ -50,6 +50,7 @@ class OasisMiniCoordinator(DataUpdateCoordinator[str]):
self.attempt = 0
await self.device.async_get_current_track_details()
await self.device.async_get_playlist_details()
await self.device.async_cloud_get_playlists()
except Exception as ex: # pylint:disable=broad-except
if self.attempt > 2 or not (data or self.data):
raise UpdateFailed(

View File

@@ -24,12 +24,14 @@
"status": {
"state": {
"booting": "mdi:loading",
"busy": "mdi:progress-clock",
"centering": "mdi:record-circle-outline",
"downloading": "mdi:progress-download",
"error": "mdi:alert-circle-outline",
"live": "mdi:pencil-circle-outline",
"paused": "mdi:motion-pause-outline",
"playing": "mdi:motion-play-outline",
"sleeping": "mdi:power-sleep",
"stopped": "mdi:stop-circle-outline",
"updating": "mdi:update"
}

View File

@@ -49,8 +49,8 @@ class OasisMiniMediaPlayerEntity(OasisMiniEntity, MediaPlayerEntity):
@property
def media_duration(self) -> int | None:
"""Duration of current playing media in seconds."""
if (track := self.device.track) and "reduced_svg_content" in track:
return track["reduced_svg_content"].get("1")
if (track := self.device.track) and "reduced_svg_content_new" in track:
return track["reduced_svg_content_new"]
return None
@property

View File

@@ -3,6 +3,7 @@
from __future__ import annotations
import asyncio
from datetime import datetime, timedelta
import logging
from typing import Any, Awaitable, Final
from urllib.parse import urljoin
@@ -10,22 +11,46 @@ from urllib.parse import urljoin
from aiohttp import ClientResponseError, ClientSession
from .const import TRACKS
from .utils import _bit_to_bool, decrypt_svg_content
from .utils import _bit_to_bool, _parse_int, decrypt_svg_content, now
_LOGGER = logging.getLogger(__name__)
STATUS_CODE_MAP = {
0: "booting", # maybe?
0: "booting",
2: "stopped",
3: "centering",
4: "playing",
5: "paused",
6: "sleeping",
9: "error",
11: "updating",
13: "downloading",
14: "busy",
15: "live",
}
ERROR_CODE_MAP = {
0: "None",
1: "Error has occurred while reading the flash memory",
2: "Error while starting the Wifi",
3: "Error when starting DNS settings for your machine",
4: "Failed to open the file to write",
5: "Not enough memory to perform the upgrade",
6: "Error while trying to upgrade your system",
7: "Error while trying to download the new version of the software",
8: "Error while reading the upgrading file",
9: "Failed to start downloading the upgrade file",
10: "Error while starting downloading the job file",
11: "Error while opening the file folder",
12: "Failed to delete a file",
13: "Error while opening the job file",
14: "You have wrong power adapter",
15: "Failed to update the device IP on Oasis Server",
16: "Your device failed centering itself",
17: "There appears to be an issue with your Oasis Device",
18: "Error while downloading the job file",
}
AUTOPLAY_MAP = {
"0": "on",
"1": "off",
@@ -51,15 +76,44 @@ LED_EFFECTS: Final[dict[str, str]] = {
"12": "Follow Rainbow",
"13": "Chasing Comet",
"14": "Gradient Follow",
"15": "Cumulative Fill",
"16": "Multi Comets A",
"17": "Rainbow Chaser",
"18": "Twinkle Lights",
"19": "Tennis Game",
"20": "Breathing Exercise 4-7-8",
"21": "Cylon Scanner",
"22": "Palette Mode",
"23": "Aurora Flow",
"24": "Colorful Drops",
"25": "Color Snake",
"26": "Flickering Candles",
"27": "Digital Rain",
"28": "Center Explosion",
"29": "Rainbow Plasma",
"30": "Comet Race",
"31": "Color Waves",
"32": "Meteor Storm",
"33": "Firefly Flicker",
"34": "Ripple",
"35": "Jelly Bean",
"36": "Forest Rain",
"37": "Multi Comets",
"38": "Multi Comets with Background",
"39": "Rainbow Fill",
"40": "White Red Comet",
"41": "Color Comets",
}
CLOUD_BASE_URL = "https://app.grounded.so"
BALL_SPEED_MAX: Final = 1000
BALL_SPEED_MIN: Final = 200
BALL_SPEED_MAX: Final = 400
BALL_SPEED_MIN: Final = 100
LED_SPEED_MAX: Final = 90
LED_SPEED_MIN: Final = -90
PLAYLISTS_REFRESH_LIMITER = timedelta(minutes=5)
class OasisMini:
"""Oasis Mini API client class."""
@@ -87,6 +141,9 @@ class OasisMini:
repeat_playlist: bool
status_code: int
playlists: list[dict[str, Any]] = []
_playlists_next_refresh: datetime = now()
def __init__(
self,
host: str,
@@ -115,10 +172,17 @@ class OasisMini:
return None
svg_content = decrypt_svg_content(svg_content)
paths = svg_content.split("L")
total = self.track.get("reduced_svg_content", {}).get("1", len(paths))
total = self.track.get("reduced_svg_content_new", 0) or len(paths)
percent = (100 * self.progress) / total
return percent
@property
def error_message(self) -> str | None:
"""Return the error message, if any."""
if self.status_code == 9:
return ERROR_CODE_MAP.get(self.error, f"Unknown ({self.error})")
return None
@property
def serial_number(self) -> str | None:
"""Return the serial number."""
@@ -210,25 +274,29 @@ class OasisMini:
raw_status = await self._async_get(params={"GETSTATUS": ""})
_LOGGER.debug("Status: %s", raw_status)
values = raw_status.split(";")
playlist = [int(track) for track in values[3].split(",") if track]
playlist = [_parse_int(track) for track in values[3].split(",") if track]
shift = len(values) - 18 if len(values) > 17 else 0
status = {
"status_code": int(values[0]), # see status code map
"error": int(values[1]), # noqa: E501; error, 0 = none, and 10 = ?, 18 = can't download?
"ball_speed": int(values[2]), # 200 - 1000
"status_code": _parse_int(values[0]), # see status code map
"error": _parse_int(values[1]),
"ball_speed": _parse_int(values[2]), # 200 - 1000
"playlist": playlist,
"playlist_index": min(int(values[4]), len(playlist)), # index of above
"progress": int(values[5]), # 0 - max svg path
"playlist_index": min(_parse_int(values[4]), len(playlist)), # noqa: E501; index of above
"progress": _parse_int(values[5]), # 0 - max svg path
"led_effect": values[6], # led effect (code lookup)
"led_color_id": values[7], # led color id?
"led_speed": int(values[8]), # -90 - 90
"brightness": int(values[9]) if values[10] else 0, # noqa: E501; 0 - 200 in app, but seems to be 0 (off) to 304 (max), then repeats
"color": values[10] or None, # hex color code
"busy": _bit_to_bool(values[11]), # noqa: E501; device is busy (downloading track, centering, software update)?
"download_progress": int(values[12]),
"max_brightness": int(values[13]),
"wifi_connected": _bit_to_bool(values[14]),
"repeat_playlist": _bit_to_bool(values[15]),
"autoplay": AUTOPLAY_MAP.get(value := values[16], value),
"led_speed": _parse_int(values[8]), # -90 - 90
"brightness": _parse_int(values[9]), # noqa: E501; 0 - 200 in app, but seems to be 0 (off) to 304 (max), then repeats
"color": values[10] if "#" in values[10] else None, # hex color code
"busy": _bit_to_bool(values[11 + shift]), # noqa: E501; device is busy (downloading track, centering, software update)?
"download_progress": _parse_int(values[12 + shift]),
"max_brightness": _parse_int(values[13 + shift]),
"wifi_connected": _bit_to_bool(values[14 + shift]),
"repeat_playlist": _bit_to_bool(values[15 + shift]),
"autoplay": AUTOPLAY_MAP.get(value := values[16 + shift], value),
"autoclean": _bit_to_bool(values[17 + shift])
if len(values) > 17
else False,
}
for key, value in status.items():
if (old_value := getattr(self, key, None)) != value:
@@ -327,6 +395,10 @@ class OasisMini:
"""Set repeat playlist."""
await self._async_command(params={"WRIREPEATJOB": 1 if repeat else 0})
async def async_sleep(self) -> None:
"""Send sleep command."""
await self._async_command(params={"CMDSLEEP": ""})
async def async_stop(self) -> None:
"""Send stop command."""
await self._async_command(params={"CMDSTOP": ""})
@@ -348,6 +420,18 @@ class OasisMini:
"""Login via the cloud."""
await self._async_cloud_request("GET", "api/auth/logout")
async def async_cloud_get_playlists(
self, personal_only: bool = False
) -> list[dict[str, Any]]:
"""Get playlists from the cloud."""
if self._playlists_next_refresh <= now():
if playlists := await self._async_cloud_request(
"GET", "api/playlist", params={"my_playlists": str(personal_only)}
):
self.playlists = playlists
self._playlists_next_refresh = now() + PLAYLISTS_REFRESH_LIMITER
return self.playlists
async def async_cloud_get_track_info(self, track_id: int) -> dict[str, Any] | None:
"""Get cloud track info."""
try:
@@ -367,7 +451,7 @@ class OasisMini:
"GET", "api/track", params={"ids[]": tracks or []}
)
if not response:
return None
return []
track_details = response.get("data", [])
while next_page_url := response.get("next_page_url"):
response = await self._async_cloud_request("GET", next_page_url)

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@
from __future__ import annotations
import base64
from datetime import UTC, datetime
import logging
import math
from xml.etree.ElementTree import Element, SubElement, tostring
@@ -26,6 +27,14 @@ def _bit_to_bool(val: str) -> bool:
return val == "1"
def _parse_int(val: str) -> int:
"""Convert an int string to int."""
try:
return int(val)
except Exception:
return 0
def draw_svg(track: dict, progress: int, model_id: str) -> str | None:
"""Draw SVG."""
if track and (svg_content := track.get("svg_content")):
@@ -33,7 +42,7 @@ def draw_svg(track: dict, progress: int, model_id: str) -> str | None:
if progress is not None:
svg_content = decrypt_svg_content(svg_content)
paths = svg_content.split("L")
total = track.get("reduced_svg_content", {}).get(model_id, len(paths))
total = track.get("reduced_svg_content_new", 0) or len(paths)
percent = min((100 * progress) / total, 100)
progress = math.floor((percent / 100) * (len(paths) - 1))
@@ -169,3 +178,7 @@ def decrypt_svg_content(svg_content: dict[str, str]):
svg_content["decrypted"] = decrypted
return decrypted
def now() -> datetime:
return datetime.now(UTC)

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
from collections import defaultdict
from dataclasses import dataclass
from typing import Any, Awaitable, Callable
@@ -63,12 +64,33 @@ class OasisMiniSelectEntity(OasisMiniEntity, SelectEntity):
return super()._handle_coordinator_update()
def playlist_update_handler(entity: OasisMiniSelectEntity) -> None:
"""Handle playlist updates."""
def playlists_update_handler(entity: OasisMiniSelectEntity) -> None:
"""Handle playlists updates."""
# pylint: disable=protected-access
device = entity.device
options = [
device._playlist.get(track, {}).get(
counts = defaultdict(int)
options = []
current_option: str | None = None
for playlist in device.playlists:
name = playlist["name"]
counts[name] += 1
if counts[name] > 1:
name = f"{name} ({counts[name]})"
options.append(name)
if device.playlist == [pattern["id"] for pattern in playlist["patterns"]]:
current_option = name
entity._attr_options = options
entity._attr_current_option = current_option
def queue_update_handler(entity: OasisMiniSelectEntity) -> None:
"""Handle queue updates."""
# pylint: disable=protected-access
device = entity.device
counts = defaultdict(int)
options = []
for track in device.playlist:
name = device._playlist.get(track, {}).get(
"name",
TRACKS.get(track, {"id": track, "name": f"Unknown Title (#{track})"}).get(
"name",
@@ -77,8 +99,10 @@ def playlist_update_handler(entity: OasisMiniSelectEntity) -> None:
else str(track),
),
)
for track in device.playlist
]
counts[name] += 1
if counts[name] > 1:
name = f"{name} ({counts[name]})"
options.append(name)
entity._attr_options = options
index = min(device.playlist_index, len(options) - 1)
entity._attr_current_option = options[index] if options else None
@@ -93,11 +117,22 @@ DESCRIPTORS = (
select_fn=lambda device, option: device.async_set_autoplay(option),
),
OasisMiniSelectEntityDescription(
key="playlist",
translation_key="playlist",
key="queue",
translation_key="queue",
current_value=lambda device: (device.playlist.copy(), device.playlist_index),
select_fn=lambda device, option: device.async_change_track(option),
update_handler=playlist_update_handler,
update_handler=queue_update_handler,
),
)
CLOUD_DESCRIPTORS = (
OasisMiniSelectEntityDescription(
key="playlists",
translation_key="playlist",
current_value=lambda device: (device.playlists, device.playlist.copy()),
select_fn=lambda device, option: device.async_set_playlist(
[pattern["id"] for pattern in device.playlists[option]["patterns"]]
),
update_handler=playlists_update_handler,
),
)
@@ -108,9 +143,13 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Oasis Mini select using config entry."""
async_add_entities(
[
OasisMiniSelectEntity(entry.runtime_data, descriptor)
for descriptor in DESCRIPTORS
]
)
coordinator: OasisMiniCoordinator = entry.runtime_data
entities = [
OasisMiniSelectEntity(coordinator, descriptor) for descriptor in DESCRIPTORS
]
if coordinator.device.access_token:
entities.extend(
OasisMiniSelectEntity(coordinator, descriptor)
for descriptor in CLOUD_DESCRIPTORS
)
async_add_entities(entities)

View File

@@ -28,10 +28,8 @@ async def async_setup_entry(
]
if coordinator.device.access_token:
entities.extend(
[
OasisMiniSensorEntity(coordinator, descriptor)
for descriptor in CLOUD_DESCRIPTORS
]
OasisMiniSensorEntity(coordinator, descriptor)
for descriptor in CLOUD_DESCRIPTORS
)
async_add_entities(entities)

View File

@@ -41,6 +41,9 @@
"button": {
"random_track": {
"name": "Play random track"
},
"sleep": {
"name": "Sleep"
}
},
"binary_sensor": {
@@ -70,6 +73,9 @@
},
"playlist": {
"name": "Playlist"
},
"queue": {
"name": "Queue"
}
},
"sensor": {
@@ -80,7 +86,28 @@
"name": "Drawing progress"
},
"error": {
"name": "Error"
"name": "Error",
"state": {
"0": "None",
"1": "Error has occurred while reading the flash memory",
"2": "Error while starting the Wifi",
"3": "Error when starting DNS settings for your machine",
"4": "Failed to open the file to write",
"5": "Not enough memory to perform the upgrade",
"6": "Error while trying to upgrade your system",
"7": "Error while trying to download the new version of the software",
"8": "Error while reading the upgrading file",
"9": "Failed to start downloading the upgrade file",
"10": "Error while starting downloading the job file",
"11": "Error while opening the file folder",
"12": "Failed to delete a file",
"13": "Error while opening the job file",
"14": "You have wrong power adapter",
"15": "Failed to update the device IP on Oasis Server",
"16": "Your device failed centering itself",
"17": "There appears to be an issue with your Oasis Device",
"18": "Error while downloading the job file"
}
},
"led_color_id": {
"name": "LED color ID"
@@ -93,9 +120,11 @@
"centering": "Centering",
"playing": "Playing",
"paused": "Paused",
"sleeping": "Sleeping",
"error": "Error",
"updating": "Updating",
"downloading": "Downloading",
"busy": "Busy",
"live": "Live drawing"
}
}

View File

@@ -41,6 +41,9 @@
"button": {
"random_track": {
"name": "Play random track"
},
"sleep": {
"name": "Sleep"
}
},
"binary_sensor": {
@@ -70,6 +73,9 @@
},
"playlist": {
"name": "Playlist"
},
"queue": {
"name": "Queue"
}
},
"sensor": {
@@ -80,7 +86,28 @@
"name": "Drawing progress"
},
"error": {
"name": "Error"
"name": "Error",
"state": {
"0": "None",
"1": "Error has occurred while reading the flash memory",
"2": "Error while starting the Wifi",
"3": "Error when starting DNS settings for your machine",
"4": "Failed to open the file to write",
"5": "Not enough memory to perform the upgrade",
"6": "Error while trying to upgrade your system",
"7": "Error while trying to download the new version of the software",
"8": "Error while reading the upgrading file",
"9": "Failed to start downloading the upgrade file",
"10": "Error while starting downloading the job file",
"11": "Error while opening the file folder",
"12": "Failed to delete a file",
"13": "Error while opening the job file",
"14": "You have wrong power adapter",
"15": "Failed to update the device IP on Oasis Server",
"16": "Your device failed centering itself",
"17": "There appears to be an issue with your Oasis Device",
"18": "Error while downloading the job file"
}
},
"led_color_id": {
"name": "LED color ID"
@@ -93,9 +120,11 @@
"centering": "Centering",
"playing": "Playing",
"paused": "Paused",
"sleeping": "Sleeping",
"error": "Error",
"updating": "Updating",
"downloading": "Downloading",
"busy": "Busy",
"live": "Live drawing"
}
}

View File

@@ -13,6 +13,12 @@ from custom_components.oasis_mini.pyoasismini.const import TRACKS
ACCESS_TOKEN = os.getenv("GROUNDED_TOKEN")
def get_author_name(data: dict) -> str:
"""Get author name from a dict."""
author = (data.get("author") or {}).get("user") or {}
return author.get("name") or author.get("nickname") or "Oasis Mini"
async def update_tracks() -> None:
"""Update tracks."""
client = OasisMini("", ACCESS_TOKEN)
@@ -32,23 +38,22 @@ async def update_tracks() -> None:
for result in filter(lambda d: d["public"], data):
if (
(track_id := result["id"]) not in TRACKS
or result["name"] != TRACKS[track_id].get("name")
or result["image"] != TRACKS[track_id].get("image")
or any(
result[field] != TRACKS[track_id].get(field)
for field in ("name", "image", "png_image")
)
or TRACKS[track_id].get("author") != get_author_name(result)
):
print(f"Updating track {track_id}: {result["name"]}")
print(f"Updating track {track_id}: {result['name']}")
track_info = await client.async_cloud_get_track_info(int(track_id))
if not track_info:
print("No track info")
break
author = (result.get("author") or {}).get("user") or {}
updated_tracks[track_id] = {
"id": track_id,
"name": result["name"],
"author": author.get("name") or author.get("nickname") or "Oasis Mini",
"image": result["image"],
"clean_pattern": track_info.get("cleanPattern", {}).get("id"),
"reduced_svg_content": track_info.get("reduced_svg_content"),
}
result["author"] = get_author_name(result)
result["reduced_svg_content_new"] = track_info.get(
"reduced_svg_content_new"
)
updated_tracks[track_id] = result
await client.session.close()
if not updated_tracks: