"id": self.id,
"name": self.name,
"timestamp": self.status.value,
+ "status": self.status.value,
}
track.sort_name = create_sort_name(track.name)
cur_item = None
async with self.mass.database.get_db() as _db:
- if track.album:
+ if track.album and not isinstance(track.album, ItemMapping):
track.album = ItemMapping.from_item(
await self.get_db_item_by_prov_id(
track.album.provider, track.album.item_id, db=_db
async with self.mass.database.get_db() as _db:
cur_item = await self.get_db_item(item_id, db=_db)
if overwrite:
- metadata = track.metadata
provider_ids = track.provider_ids
track_artists = track.artists
+ track_album = track.album or cur_item.album
else:
- metadata = cur_item.metadata.update(track.metadata)
provider_ids = {*cur_item.provider_ids, *track.provider_ids}
- track_artists = await self._get_track_artists(track, cur_item.artists)
- if track.album and not isinstance(track.album, ItemMapping):
- track.album = ItemMapping.from_item(
+ track_artists = cur_item.artists + track.artists
+ track_album = cur_item.album or track.album
+ metadata = cur_item.metadata.update(track.metadata, overwrite)
+ if track_album and not isinstance(track_album, ItemMapping):
+ track_album = ItemMapping.from_item(
await self.get_db_item_by_prov_id(
- track.album.provider, track.album.item_id, db=_db
+ track_album.provider, track_album.item_id, db=_db
)
- or await self.mass.music.albums.add_db_item(track.album)
+ or await self.mass.music.albums.add_db_item(track_album)
)
# we store a mapping to artists on the track for easier access/listings
- track_artists = await self._get_track_artists(track, cur_item.artists)
+ track_artists = await self._get_track_artists(track, track_artists)
await self.mass.database.update(
self.db_table,
{"item_id": item_id},
"version": track.version if overwrite else cur_item.version,
"duration": track.duration if overwrite else cur_item.duration,
"artists": json_serializer(track_artists),
+ "album": json_serializer(track_album),
"metadata": json_serializer(metadata),
"provider_ids": json_serializer(provider_ids),
"isrc": track.isrc or cur_item.isrc,
import unidecode
+from music_assistant.models.media_items import ItemMapping
+
if TYPE_CHECKING:
from music_assistant.models.media_items import (
Album,
and left_album.item_id == right_album.item_id
):
return True
- if left_album.upc and right_album.upc:
- if (left_album.upc in right_album.upc) or (right_album.upc in left_album.upc):
- # UPC is always 100% accurate match
- return True
- if left_album.musicbrainz_id and right_album.musicbrainz_id:
- if left_album.musicbrainz_id == right_album.musicbrainz_id:
- # musicbrainz_id is always 100% accurate match
- return True
+ if not (
+ isinstance(left_album, ItemMapping) or isinstance(right_album, ItemMapping)
+ ):
+ if left_album.upc and right_album.upc:
+ if (left_album.upc in right_album.upc) or (
+ right_album.upc in left_album.upc
+ ):
+ # UPC is always 100% accurate match
+ return True
+ if left_album.musicbrainz_id and right_album.musicbrainz_id:
+ if left_album.musicbrainz_id == right_album.musicbrainz_id:
+ # musicbrainz_id is always 100% accurate match
+ return True
if not compare_strings(left_album.name, right_album.name):
return False
if not compare_version(left_album.version, right_album.version):
# pylint: disable=invalid-name
-SCHEMA_VERSION = 3
+SCHEMA_VERSION = 4
TABLE_PROV_MAPPINGS = "provider_mappings"
TABLE_TRACK_LOUDNESS = "track_loudness"
TABLE_TRACKS = "tracks"
TABLE_PLAYLISTS = "playlists"
TABLE_RADIOS = "radios"
+TABLE_CACHE = "cache"
class Database:
if prev_version < 3:
# schema version 3: too many breaking changes, rebuild db
async with self.get_db() as _db:
- await _db.execute("DROP TABLE IF EXISTS artists")
- await _db.execute("DROP TABLE IF EXISTS albums")
+ await _db.execute(f"DROP TABLE IF EXISTS {TABLE_ARTISTS}")
+ await _db.execute(f"DROP TABLE IF EXISTS {TABLE_ALBUMS}")
+ await _db.execute(f"DROP TABLE IF EXISTS {TABLE_TRACKS}")
+ await _db.execute(f"DROP TABLE IF EXISTS {TABLE_PLAYLISTS}")
+ await _db.execute(f"DROP TABLE IF EXISTS {TABLE_RADIOS}")
+ await _db.execute(f"DROP TABLE IF EXISTS {TABLE_PROV_MAPPINGS}")
+ await _db.execute(f"DROP TABLE IF EXISTS {TABLE_CACHE}")
+
+ if prev_version < 4:
+ # schema version 4: add album to tracks table
+ async with self.get_db() as _db:
await _db.execute("DROP TABLE IF EXISTS tracks")
- await _db.execute("DROP TABLE IF EXISTS playlists")
- await _db.execute("DROP TABLE IF EXISTS radios")
- await _db.execute("DROP TABLE IF EXISTS playlist_tracks")
- await _db.execute("DROP TABLE IF EXISTS album_tracks")
- await _db.execute("DROP TABLE IF EXISTS provider_mappings")
- await _db.execute("DROP TABLE IF EXISTS cache")
+ if await self.exists(TABLE_PROV_MAPPINGS, _db):
+ await self.delete(
+ TABLE_PROV_MAPPINGS, {"media_type": "track"}, db=_db
+ )
# create db tables
await self.__create_database_tables()
isrc TEXT,
musicbrainz_id TEXT,
artists json,
+ album json,
metadata json,
provider_ids json
);"""
def __job_done_cb(self, task: asyncio.Task, job: BackgroundJob):
"""Call when background job finishes."""
+ execution_time = round(time() - job.timestamp, 2)
+ job.timestamp = execution_time
if task.cancelled():
job.status = JobStatus.CANCELLED
self.logger.debug("Job [%s] is cancelled.", job.name)
)
else:
job.status = JobStatus.FINISHED
- execution_time = round(time() - job.timestamp, 2)
self.logger.info(
"Finished job [%s] in %s seconds.", job.name, execution_time
)
MetadataTypes = Union[int, bool, str, List[str]]
-JSON_KEYS = ("artists", "artist", "metadata", "provider_ids")
+JSON_KEYS = ("artists", "artist", "album", "metadata", "provider_ids")
class MediaType(Enum):
db_row = dict(db_row)
db_row["provider"] = "database"
for key in JSON_KEYS:
- if key in db_row:
+ if key in db_row and db_row[key] is not None:
db_row[key] = json.loads(db_row[key])
if "in_library" in db_row:
db_row["in_library"] = bool(db_row["in_library"])
MediaItemProviderId(provider=self.id, item_id=prov_item_id, url=filename)
)
playlist.owner = self._attr_name
- playlist.checksum = os.path.getmtime(filename)
+ playlist.checksum = str(os.path.getmtime(filename))
return playlist
async def _parse_track_from_uri(self, uri):
if track_obj.get("isrc"):
track.isrc = track_obj["isrc"]
if track_obj.get("performers"):
- track.metadata.performers = track_obj["performers"]
+ track.metadata.performers = {
+ x.strip() for x in track_obj["performers"].split("-")
+ }
if track_obj.get("copyright"):
track.metadata.copyright = track_obj["copyright"]
if track_obj.get("audio_info"):
)
if img := self.__get_image(playlist_obj):
playlist.metadata.images = {MediaItemImage(ImageType.THUMB, img)}
- playlist.checksum = playlist_obj["updated_at"]
+ playlist.checksum = str(playlist_obj["updated_at"])
return playlist
async def _auth_token(self):
playlist.metadata.images = {
MediaItemImage(ImageType.THUMB, playlist_obj["images"][0]["url"])
}
- playlist.checksum = playlist_obj["snapshot_id"]
+ playlist.checksum = str(playlist_obj["snapshot_id"])
return playlist
async def get_token(self):