package dev.stave.domain.model
import kotlin.time.Duration
/**
* A single entry returned by a ContentDirectory Browse() call.
* Either a Container (navigable) or a Track (playable) — images/playlists come later.
*
* The [extras] map preserves non-standard properties MinimServer exposes via
* upnp:userAnnotation — Work, Group, DiscSubtitle, etc. — so the UI can render them
* without the parser having to know the full list ahead of time.
*/
sealed class DidlItem {
abstract val id: String
abstract val parentId: String
abstract val title: String
abstract val extras: Map<String, String>
data class Container(
override val id: String,
override val parentId: String,
override val title: String,
val childCount: Int?,
val albumArtUri: String?,
val upnpClass: String,
/** Album artist / creator for album containers — null for generic folders. */
val artist: String?,
/** True for MinimServer's synthetic entries: `>> Show All`, `[folder view]`, disc headings. */
val isSynthetic: Boolean,
override val extras: Map<String, String> = emptyMap(),
) : DidlItem() {
val isAlbum: Boolean get() = upnpClass.contains("musicAlbum", ignoreCase = true)
/** True when this container represents a genre classification.
* UPnP musicGenre class (object.container.genre.musicGenre) is
* used by MinimServer, BubbleUPnP Server, and most other
* ContentDirectory implementations for genre-level browsing. */
val isGenre: Boolean get() = upnpClass.contains("genre", ignoreCase = true)
}
data class Track(
override val id: String,
override val parentId: String,
override val title: String,
val artist: String?,
val albumArtist: String?,
val album: String?,
val composer: String?,
val genre: String?,
val date: String?,
val trackNumber: Int?,
val discNumber: Int?,
val duration: Duration?,
val albumArtUri: String?,
val resourceUrl: String,
val mimeType: String?,
val sampleFrequency: Int?,
val bitsPerSample: Int?,
val channels: Int?,
val bitrate: Int?,
val size: Long?,
override val extras: Map<String, String> = emptyMap(),
) : DidlItem() {
val isLossless: Boolean get() = mimeType?.let {
it.contains("flac") || it.contains("wav") || it.contains("alac") || it.contains("aiff")
} ?: false
}
}