"""Entity → Subsonic child-dict mappers (presentation only). Pure functions turning domain entities into the attribute dicts the envelope serializer renders as ```` / ```` / ```` elements (or their JSON equivalents). No business logic — they only reshape and rename. """ import datetime as dt from typing import Any from app.api.rest.ids import encode_album, encode_artist, encode_track from app.domain.entities import Album, Artist, Track # Suffix → MIME, for the ``contentType``/``suffix`` song attributes. A # presentation detail (mirrors StreamingService's content-type negotiation). _CONTENT_TYPE: dict[str, str] = { "mp3": "audio/mpeg", "flac": "audio/flac", "m4a": "audio/mp4", "aac": "audio/aac", "ogg": "audio/ogg", "opus": "audio/ogg", "wav": "audio/wav", "aiff": "audio/aiff", "aif": "audio/aiff", } def iso(value: dt.datetime) -> str: return value.astimezone(dt.UTC).strftime("%Y-%m-%dT%H:%M:%S.000Z") def content_type_for(file_format: str) -> str: return _CONTENT_TYPE.get(file_format.lower(), "application/octet-stream") def artist_dict(artist: Artist, *, album_count: int) -> dict[str, Any]: return { "id": encode_artist(artist.id), "name": artist.name, "albumCount": album_count, "coverArt": encode_artist(artist.id), } def album_dict( album: Album, artist: Artist | None, *, song_count: int, duration: int | None = None, ) -> dict[str, Any]: return { "id": encode_album(album.id), "name": album.title, "title": album.title, "artist": artist.name if artist is not None else None, "artistId": encode_artist(album.artist_id), "coverArt": encode_album(album.id), "songCount": song_count, "duration": duration, "created": iso(album.created_at), "year": album.year, } def song_dict( track: Track, artist: Artist | None, album: Album | None, ) -> dict[str, Any]: cover = encode_album(track.album_id) if track.album_id is not None else encode_track(track.id) return { "id": encode_track(track.id), "parent": encode_album(track.album_id) if track.album_id is not None else None, "isDir": False, "title": track.title, "album": album.title if album is not None else None, "artist": artist.name if artist is not None else None, "albumId": encode_album(track.album_id) if track.album_id is not None else None, "artistId": encode_artist(track.artist_id), "coverArt": cover, "size": track.file_size or 0, "contentType": content_type_for(track.file_format or ""), "suffix": track.file_format, "duration": track.duration_seconds, "year": track.year, "genre": track.genre, "created": iso(track.created_at), "type": "music", "isVideo": False, }