musikr: refactor model
This commit is contained in:
parent
1d0ad641d5
commit
501c79d23c
12 changed files with 470 additions and 371 deletions
|
@ -29,9 +29,9 @@ import org.oxycblt.musikr.Indexer
|
||||||
import org.oxycblt.musikr.IndexingProgress
|
import org.oxycblt.musikr.IndexingProgress
|
||||||
import org.oxycblt.musikr.Library
|
import org.oxycblt.musikr.Library
|
||||||
import org.oxycblt.musikr.Music
|
import org.oxycblt.musikr.Music
|
||||||
|
import org.oxycblt.musikr.MutableLibrary
|
||||||
import org.oxycblt.musikr.Playlist
|
import org.oxycblt.musikr.Playlist
|
||||||
import org.oxycblt.musikr.Song
|
import org.oxycblt.musikr.Song
|
||||||
import org.oxycblt.musikr.model.MutableLibrary
|
|
||||||
import org.oxycblt.musikr.tag.Interpretation
|
import org.oxycblt.musikr.tag.Interpretation
|
||||||
import org.oxycblt.musikr.tag.Name
|
import org.oxycblt.musikr.tag.Name
|
||||||
import org.oxycblt.musikr.tag.interpret.Separators
|
import org.oxycblt.musikr.tag.interpret.Separators
|
||||||
|
|
|
@ -26,7 +26,6 @@ import kotlinx.coroutines.flow.onCompletion
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
import org.oxycblt.musikr.fs.MusicLocation
|
import org.oxycblt.musikr.fs.MusicLocation
|
||||||
import org.oxycblt.musikr.model.MutableLibrary
|
|
||||||
import org.oxycblt.musikr.pipeline.EvaluateStep
|
import org.oxycblt.musikr.pipeline.EvaluateStep
|
||||||
import org.oxycblt.musikr.pipeline.ExploreStep
|
import org.oxycblt.musikr.pipeline.ExploreStep
|
||||||
import org.oxycblt.musikr.pipeline.ExtractStep
|
import org.oxycblt.musikr.pipeline.ExtractStep
|
||||||
|
|
|
@ -61,6 +61,18 @@ interface Library {
|
||||||
fun findPlaylistByName(name: String): Playlist?
|
fun findPlaylistByName(name: String): Playlist?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MutableLibrary : Library {
|
||||||
|
suspend fun createPlaylist(name: String, songs: List<Song>): MutableLibrary
|
||||||
|
|
||||||
|
suspend fun renamePlaylist(playlist: Playlist, name: String): MutableLibrary
|
||||||
|
|
||||||
|
suspend fun addToPlaylist(playlist: Playlist, songs: List<Song>): MutableLibrary
|
||||||
|
|
||||||
|
suspend fun rewritePlaylist(playlist: Playlist, songs: List<Song>): MutableLibrary
|
||||||
|
|
||||||
|
suspend fun deletePlaylist(playlist: Playlist): MutableLibrary
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract music data. This contains universal information about all concrete music
|
* Abstract music data. This contains universal information about all concrete music
|
||||||
* implementations, such as identification information and names.
|
* implementations, such as identification information and names.
|
||||||
|
|
77
app/src/main/java/org/oxycblt/musikr/model/AlbumImpl.kt
Normal file
77
app/src/main/java/org/oxycblt/musikr/model/AlbumImpl.kt
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Auxio Project
|
||||||
|
* AlbumImpl.kt is part of Auxio.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.oxycblt.musikr.model
|
||||||
|
|
||||||
|
import org.oxycblt.auxio.music.MusicType
|
||||||
|
import org.oxycblt.auxio.util.update
|
||||||
|
import org.oxycblt.musikr.Album
|
||||||
|
import org.oxycblt.musikr.Artist
|
||||||
|
import org.oxycblt.musikr.Music
|
||||||
|
import org.oxycblt.musikr.Song
|
||||||
|
import org.oxycblt.musikr.cover.Cover
|
||||||
|
import org.oxycblt.musikr.tag.Date
|
||||||
|
import org.oxycblt.musikr.tag.interpret.PreAlbum
|
||||||
|
|
||||||
|
interface AlbumCore {
|
||||||
|
val preAlbum: PreAlbum
|
||||||
|
val songs: List<Song>
|
||||||
|
|
||||||
|
fun resolveArtists(): List<Artist>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Library-backed implementation of [Album].
|
||||||
|
*
|
||||||
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
|
*/
|
||||||
|
class AlbumImpl(private val core: AlbumCore) : Album {
|
||||||
|
private val preAlbum = core.preAlbum
|
||||||
|
|
||||||
|
override val uid =
|
||||||
|
// Attempt to use a MusicBrainz ID first before falling back to a hashed UID.
|
||||||
|
preAlbum.musicBrainzId?.let { Music.UID.musicBrainz(MusicType.ALBUMS, it) }
|
||||||
|
?: Music.UID.auxio(MusicType.ALBUMS) {
|
||||||
|
// Hash based on only names despite the presence of a date to increase stability.
|
||||||
|
// I don't know if there is any situation where an artist will have two albums with
|
||||||
|
// the exact same name, but if there is, I would love to know.
|
||||||
|
update(preAlbum.rawName)
|
||||||
|
update(preAlbum.preArtists.map { it.rawName })
|
||||||
|
}
|
||||||
|
override val name = preAlbum.name
|
||||||
|
override val releaseType = preAlbum.releaseType
|
||||||
|
override val durationMs = core.songs.sumOf { it.durationMs }
|
||||||
|
override val dateAdded = core.songs.minOf { it.dateAdded }
|
||||||
|
override val cover = Cover.multi(core.songs)
|
||||||
|
override val dates: Date.Range? =
|
||||||
|
core.songs.mapNotNull { it.date }.ifEmpty { null }?.run { Date.Range(min(), max()) }
|
||||||
|
|
||||||
|
override val artists: List<Artist>
|
||||||
|
get() = core.resolveArtists()
|
||||||
|
|
||||||
|
override val songs = core.songs
|
||||||
|
|
||||||
|
private val hashCode = 31 * (31 * uid.hashCode() + preAlbum.hashCode()) + songs.hashCode()
|
||||||
|
|
||||||
|
override fun hashCode() = hashCode
|
||||||
|
|
||||||
|
override fun equals(other: Any?) =
|
||||||
|
other is AlbumImpl && uid == other.uid && preAlbum == other.preAlbum && songs == other.songs
|
||||||
|
|
||||||
|
override fun toString() = "Album(uid=$uid, name=$name)"
|
||||||
|
}
|
73
app/src/main/java/org/oxycblt/musikr/model/ArtistImpl.kt
Normal file
73
app/src/main/java/org/oxycblt/musikr/model/ArtistImpl.kt
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Auxio Project
|
||||||
|
* ArtistImpl.kt is part of Auxio.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.oxycblt.musikr.model
|
||||||
|
|
||||||
|
import org.oxycblt.auxio.music.MusicType
|
||||||
|
import org.oxycblt.auxio.util.update
|
||||||
|
import org.oxycblt.musikr.Album
|
||||||
|
import org.oxycblt.musikr.Artist
|
||||||
|
import org.oxycblt.musikr.Genre
|
||||||
|
import org.oxycblt.musikr.Music
|
||||||
|
import org.oxycblt.musikr.Song
|
||||||
|
import org.oxycblt.musikr.cover.Cover
|
||||||
|
import org.oxycblt.musikr.tag.interpret.PreArtist
|
||||||
|
|
||||||
|
interface ArtistCore {
|
||||||
|
val preArtist: PreArtist
|
||||||
|
val songs: Set<Song>
|
||||||
|
val albums: Set<Album>
|
||||||
|
|
||||||
|
fun resolveGenres(): Set<Genre>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Library-backed implementation of [Artist].
|
||||||
|
*
|
||||||
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
|
*/
|
||||||
|
class ArtistImpl(private val core: ArtistCore) : Artist {
|
||||||
|
override val uid =
|
||||||
|
// Attempt to use a MusicBrainz ID first before falling back to a hashed UID.
|
||||||
|
core.preArtist.musicBrainzId?.let { Music.UID.musicBrainz(MusicType.ARTISTS, it) }
|
||||||
|
?: Music.UID.auxio(MusicType.ARTISTS) { update(core.preArtist.rawName) }
|
||||||
|
override val name = core.preArtist.name
|
||||||
|
|
||||||
|
override val songs = core.songs
|
||||||
|
override var explicitAlbums = core.albums
|
||||||
|
override var implicitAlbums = core.songs.mapTo(mutableSetOf()) { it.album } - core.albums
|
||||||
|
|
||||||
|
override val genres: List<Genre>
|
||||||
|
get() = core.resolveGenres().toList()
|
||||||
|
|
||||||
|
override val durationMs = core.songs.sumOf { it.durationMs }
|
||||||
|
override val cover = Cover.multi(core.songs)
|
||||||
|
|
||||||
|
private val hashCode =
|
||||||
|
31 * (31 * uid.hashCode() + core.preArtist.hashCode()) * core.songs.hashCode()
|
||||||
|
|
||||||
|
override fun hashCode() = hashCode
|
||||||
|
|
||||||
|
override fun equals(other: Any?) =
|
||||||
|
other is ArtistImpl &&
|
||||||
|
uid == other.uid &&
|
||||||
|
core.preArtist == other.core.preArtist &&
|
||||||
|
songs == other.songs
|
||||||
|
|
||||||
|
override fun toString() = "Artist(uid=$uid, name=$name)"
|
||||||
|
}
|
|
@ -1,210 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2023 Auxio Project
|
|
||||||
* DeviceMusicImpl.kt is part of Auxio.
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.oxycblt.musikr.model
|
|
||||||
|
|
||||||
import org.oxycblt.auxio.music.MusicType
|
|
||||||
import org.oxycblt.auxio.util.update
|
|
||||||
import org.oxycblt.musikr.Album
|
|
||||||
import org.oxycblt.musikr.Artist
|
|
||||||
import org.oxycblt.musikr.Genre
|
|
||||||
import org.oxycblt.musikr.Music
|
|
||||||
import org.oxycblt.musikr.Song
|
|
||||||
import org.oxycblt.musikr.cover.Cover
|
|
||||||
import org.oxycblt.musikr.tag.Date
|
|
||||||
import org.oxycblt.musikr.tag.interpret.PreAlbum
|
|
||||||
import org.oxycblt.musikr.tag.interpret.PreArtist
|
|
||||||
import org.oxycblt.musikr.tag.interpret.PreGenre
|
|
||||||
import org.oxycblt.musikr.tag.interpret.PreSong
|
|
||||||
|
|
||||||
interface SongHandle {
|
|
||||||
val preSong: PreSong
|
|
||||||
|
|
||||||
fun resolveAlbum(): Album
|
|
||||||
|
|
||||||
fun resolveArtists(): List<Artist>
|
|
||||||
|
|
||||||
fun resolveGenres(): List<Genre>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Library-backed implementation of [Song].
|
|
||||||
*
|
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
|
||||||
*/
|
|
||||||
class SongImpl(private val handle: SongHandle) : Song {
|
|
||||||
private val preSong = handle.preSong
|
|
||||||
|
|
||||||
override val uid = preSong.computeUid()
|
|
||||||
override val name = preSong.name
|
|
||||||
override val track = preSong.track
|
|
||||||
override val disc = preSong.disc
|
|
||||||
override val date = preSong.date
|
|
||||||
override val uri = preSong.uri
|
|
||||||
override val path = preSong.path
|
|
||||||
override val mimeType = preSong.mimeType
|
|
||||||
override val size = preSong.size
|
|
||||||
override val durationMs = preSong.durationMs
|
|
||||||
override val replayGainAdjustment = preSong.replayGainAdjustment
|
|
||||||
override val lastModified = preSong.lastModified
|
|
||||||
override val dateAdded = preSong.dateAdded
|
|
||||||
override val cover = Cover.single(this)
|
|
||||||
override val album: Album
|
|
||||||
get() = handle.resolveAlbum()
|
|
||||||
|
|
||||||
override val artists: List<Artist>
|
|
||||||
get() = handle.resolveArtists()
|
|
||||||
|
|
||||||
override val genres: List<Genre>
|
|
||||||
get() = handle.resolveGenres()
|
|
||||||
|
|
||||||
private val hashCode = 31 * uid.hashCode() + preSong.hashCode()
|
|
||||||
|
|
||||||
override fun hashCode() = hashCode
|
|
||||||
|
|
||||||
override fun equals(other: Any?) =
|
|
||||||
other is SongImpl && uid == other.uid && preSong == other.preSong
|
|
||||||
|
|
||||||
override fun toString() = "Song(uid=$uid, name=$name)"
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AlbumHandle {
|
|
||||||
val preAlbum: PreAlbum
|
|
||||||
val songs: List<Song>
|
|
||||||
|
|
||||||
fun resolveArtists(): List<Artist>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Library-backed implementation of [Album].
|
|
||||||
*
|
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
|
||||||
*/
|
|
||||||
class AlbumImpl(private val handle: AlbumHandle) : Album {
|
|
||||||
private val preAlbum = handle.preAlbum
|
|
||||||
|
|
||||||
override val uid =
|
|
||||||
// Attempt to use a MusicBrainz ID first before falling back to a hashed UID.
|
|
||||||
preAlbum.musicBrainzId?.let { Music.UID.musicBrainz(MusicType.ALBUMS, it) }
|
|
||||||
?: Music.UID.auxio(MusicType.ALBUMS) {
|
|
||||||
// Hash based on only names despite the presence of a date to increase stability.
|
|
||||||
// I don't know if there is any situation where an artist will have two albums with
|
|
||||||
// the exact same name, but if there is, I would love to know.
|
|
||||||
update(preAlbum.rawName)
|
|
||||||
update(preAlbum.preArtists.map { it.rawName })
|
|
||||||
}
|
|
||||||
override val name = preAlbum.name
|
|
||||||
override val releaseType = preAlbum.releaseType
|
|
||||||
override val durationMs = handle.songs.sumOf { it.durationMs }
|
|
||||||
override val dateAdded = handle.songs.minOf { it.dateAdded }
|
|
||||||
override val cover = Cover.multi(handle.songs)
|
|
||||||
override val dates: Date.Range? =
|
|
||||||
handle.songs.mapNotNull { it.date }.ifEmpty { null }?.run { Date.Range(min(), max()) }
|
|
||||||
|
|
||||||
override val artists: List<Artist>
|
|
||||||
get() = handle.resolveArtists()
|
|
||||||
|
|
||||||
override val songs = handle.songs
|
|
||||||
|
|
||||||
private val hashCode = 31 * (31 * uid.hashCode() + preAlbum.hashCode()) + songs.hashCode()
|
|
||||||
|
|
||||||
override fun hashCode() = hashCode
|
|
||||||
|
|
||||||
override fun equals(other: Any?) =
|
|
||||||
other is AlbumImpl && uid == other.uid && preAlbum == other.preAlbum && songs == other.songs
|
|
||||||
|
|
||||||
override fun toString() = "Album(uid=$uid, name=$name)"
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ArtistHandle {
|
|
||||||
val preArtist: PreArtist
|
|
||||||
val songs: Set<Song>
|
|
||||||
val albums: Set<Album>
|
|
||||||
|
|
||||||
fun resolveGenres(): Set<Genre>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Library-backed implementation of [Artist].
|
|
||||||
*
|
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
|
||||||
*/
|
|
||||||
class ArtistImpl(private val handle: ArtistHandle) : Artist {
|
|
||||||
override val uid =
|
|
||||||
// Attempt to use a MusicBrainz ID first before falling back to a hashed UID.
|
|
||||||
handle.preArtist.musicBrainzId?.let { Music.UID.musicBrainz(MusicType.ARTISTS, it) }
|
|
||||||
?: Music.UID.auxio(MusicType.ARTISTS) { update(handle.preArtist.rawName) }
|
|
||||||
override val name = handle.preArtist.name
|
|
||||||
|
|
||||||
override val songs = handle.songs
|
|
||||||
override var explicitAlbums = handle.albums
|
|
||||||
override var implicitAlbums = handle.songs.mapTo(mutableSetOf()) { it.album } - handle.albums
|
|
||||||
|
|
||||||
override val genres: List<Genre>
|
|
||||||
get() = handle.resolveGenres().toList()
|
|
||||||
|
|
||||||
override val durationMs = handle.songs.sumOf { it.durationMs }
|
|
||||||
override val cover = Cover.multi(handle.songs)
|
|
||||||
|
|
||||||
private val hashCode =
|
|
||||||
31 * (31 * uid.hashCode() + handle.preArtist.hashCode()) * handle.songs.hashCode()
|
|
||||||
|
|
||||||
override fun hashCode() = hashCode
|
|
||||||
|
|
||||||
override fun equals(other: Any?) =
|
|
||||||
other is ArtistImpl &&
|
|
||||||
uid == other.uid &&
|
|
||||||
handle.preArtist == other.handle.preArtist &&
|
|
||||||
songs == other.songs
|
|
||||||
|
|
||||||
override fun toString() = "Artist(uid=$uid, name=$name)"
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GenreHandle {
|
|
||||||
val preGenre: PreGenre
|
|
||||||
val songs: Set<Song>
|
|
||||||
val artists: Set<Artist>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Library-backed implementation of [Genre].
|
|
||||||
*
|
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
|
||||||
*/
|
|
||||||
class GenreImpl(private val handle: GenreHandle) : Genre {
|
|
||||||
override val uid = Music.UID.auxio(MusicType.GENRES) { update(handle.preGenre.rawName) }
|
|
||||||
override val name = handle.preGenre.name
|
|
||||||
|
|
||||||
override val songs = mutableSetOf<Song>()
|
|
||||||
override val artists = mutableSetOf<Artist>()
|
|
||||||
override val durationMs = handle.songs.sumOf { it.durationMs }
|
|
||||||
override val cover = Cover.multi(handle.songs)
|
|
||||||
|
|
||||||
private val hashCode =
|
|
||||||
31 * (31 * uid.hashCode() + handle.preGenre.hashCode()) + songs.hashCode()
|
|
||||||
|
|
||||||
override fun hashCode() = hashCode
|
|
||||||
|
|
||||||
override fun equals(other: Any?) =
|
|
||||||
other is GenreImpl &&
|
|
||||||
uid == other.uid &&
|
|
||||||
handle.preGenre == other.handle.preGenre &&
|
|
||||||
songs == other.songs
|
|
||||||
|
|
||||||
override fun toString() = "Genre(uid=$uid, name=$name)"
|
|
||||||
}
|
|
61
app/src/main/java/org/oxycblt/musikr/model/GenreImpl.kt
Normal file
61
app/src/main/java/org/oxycblt/musikr/model/GenreImpl.kt
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Auxio Project
|
||||||
|
* GenreImpl.kt is part of Auxio.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.oxycblt.musikr.model
|
||||||
|
|
||||||
|
import org.oxycblt.auxio.music.MusicType
|
||||||
|
import org.oxycblt.auxio.util.update
|
||||||
|
import org.oxycblt.musikr.Artist
|
||||||
|
import org.oxycblt.musikr.Genre
|
||||||
|
import org.oxycblt.musikr.Music
|
||||||
|
import org.oxycblt.musikr.Song
|
||||||
|
import org.oxycblt.musikr.cover.Cover
|
||||||
|
import org.oxycblt.musikr.tag.interpret.PreGenre
|
||||||
|
|
||||||
|
interface GenreCore {
|
||||||
|
val preGenre: PreGenre
|
||||||
|
val songs: Set<Song>
|
||||||
|
val artists: Set<Artist>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Library-backed implementation of [Genre].
|
||||||
|
*
|
||||||
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
|
*/
|
||||||
|
class GenreImpl(private val core: GenreCore) : Genre {
|
||||||
|
override val uid = Music.UID.auxio(MusicType.GENRES) { update(core.preGenre.rawName) }
|
||||||
|
override val name = core.preGenre.name
|
||||||
|
|
||||||
|
override val songs = mutableSetOf<Song>()
|
||||||
|
override val artists = mutableSetOf<Artist>()
|
||||||
|
override val durationMs = core.songs.sumOf { it.durationMs }
|
||||||
|
override val cover = Cover.multi(core.songs)
|
||||||
|
|
||||||
|
private val hashCode = 31 * (31 * uid.hashCode() + core.preGenre.hashCode()) + songs.hashCode()
|
||||||
|
|
||||||
|
override fun hashCode() = hashCode
|
||||||
|
|
||||||
|
override fun equals(other: Any?) =
|
||||||
|
other is GenreImpl &&
|
||||||
|
uid == other.uid &&
|
||||||
|
core.preGenre == other.core.preGenre &&
|
||||||
|
songs == other.songs
|
||||||
|
|
||||||
|
override fun toString() = "Genre(uid=$uid, name=$name)"
|
||||||
|
}
|
|
@ -1,158 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2024 Auxio Project
|
|
||||||
* Library.kt is part of Auxio.
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.oxycblt.musikr.model
|
|
||||||
|
|
||||||
import javax.inject.Inject
|
|
||||||
import org.oxycblt.musikr.Album
|
|
||||||
import org.oxycblt.musikr.Artist
|
|
||||||
import org.oxycblt.musikr.Genre
|
|
||||||
import org.oxycblt.musikr.Library
|
|
||||||
import org.oxycblt.musikr.Music
|
|
||||||
import org.oxycblt.musikr.Playlist
|
|
||||||
import org.oxycblt.musikr.Song
|
|
||||||
import org.oxycblt.musikr.fs.Path
|
|
||||||
import org.oxycblt.musikr.graph.AlbumVertex
|
|
||||||
import org.oxycblt.musikr.graph.ArtistVertex
|
|
||||||
import org.oxycblt.musikr.graph.GenreVertex
|
|
||||||
import org.oxycblt.musikr.graph.MusicGraph
|
|
||||||
import org.oxycblt.musikr.graph.SongVertex
|
|
||||||
|
|
||||||
interface MutableLibrary : Library {
|
|
||||||
suspend fun createPlaylist(name: String, songs: List<Song>): MutableLibrary
|
|
||||||
|
|
||||||
suspend fun renamePlaylist(playlist: Playlist, name: String): MutableLibrary
|
|
||||||
|
|
||||||
suspend fun addToPlaylist(playlist: Playlist, songs: List<Song>): MutableLibrary
|
|
||||||
|
|
||||||
suspend fun rewritePlaylist(playlist: Playlist, songs: List<Song>): MutableLibrary
|
|
||||||
|
|
||||||
suspend fun deletePlaylist(playlist: Playlist): MutableLibrary
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LibraryFactory {
|
|
||||||
fun create(graph: MusicGraph): MutableLibrary
|
|
||||||
}
|
|
||||||
|
|
||||||
class LibraryFactoryImpl @Inject constructor() : LibraryFactory {
|
|
||||||
override fun create(graph: MusicGraph): MutableLibrary {
|
|
||||||
val songs =
|
|
||||||
graph.songVertex.mapTo(mutableSetOf()) { vertex ->
|
|
||||||
SongImpl(SongVertexHandle(vertex)).also { vertex.tag = it }
|
|
||||||
}
|
|
||||||
val albums =
|
|
||||||
graph.albumVertex.mapTo(mutableSetOf()) { vertex ->
|
|
||||||
AlbumImpl(AlbumVertexHandle(vertex)).also { vertex.tag = it }
|
|
||||||
}
|
|
||||||
val artists =
|
|
||||||
graph.artistVertex.mapTo(mutableSetOf()) { vertex ->
|
|
||||||
ArtistImpl(ArtistVertexHandle(vertex)).also { vertex.tag = it }
|
|
||||||
}
|
|
||||||
val genres =
|
|
||||||
graph.genreVertex.mapTo(mutableSetOf()) { vertex ->
|
|
||||||
GenreImpl(GenreVertexHandle(vertex)).also { vertex.tag = it }
|
|
||||||
}
|
|
||||||
return LibraryImpl(songs, albums, artists, genres)
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SongVertexHandle(private val vertex: SongVertex) : SongHandle {
|
|
||||||
override val preSong = vertex.preSong
|
|
||||||
|
|
||||||
override fun resolveAlbum() = vertex.albumVertex.tag as Album
|
|
||||||
|
|
||||||
override fun resolveArtists() = vertex.artistVertices.map { it.tag as Artist }
|
|
||||||
|
|
||||||
override fun resolveGenres() = vertex.genreVertices.map { it.tag as Genre }
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AlbumVertexHandle(private val vertex: AlbumVertex) : AlbumHandle {
|
|
||||||
override val preAlbum = vertex.preAlbum
|
|
||||||
|
|
||||||
override val songs = vertex.songVertices.map { SongImpl(SongVertexHandle(it)) }
|
|
||||||
|
|
||||||
override fun resolveArtists() = vertex.artistVertices.map { it.tag as Artist }
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ArtistVertexHandle(private val vertex: ArtistVertex) : ArtistHandle {
|
|
||||||
override val preArtist = vertex.preArtist
|
|
||||||
|
|
||||||
override val songs = vertex.songVertices.mapTo(mutableSetOf()) { it.tag as Song }
|
|
||||||
|
|
||||||
override val albums = vertex.albumVertices.mapTo(mutableSetOf()) { it.tag as Album }
|
|
||||||
|
|
||||||
override fun resolveGenres() =
|
|
||||||
vertex.genreVertices.mapTo(mutableSetOf()) { it.tag as Genre }
|
|
||||||
}
|
|
||||||
|
|
||||||
private class GenreVertexHandle(vertex: GenreVertex) : GenreHandle {
|
|
||||||
override val preGenre = vertex.preGenre
|
|
||||||
|
|
||||||
override val songs = vertex.songVertices.mapTo(mutableSetOf()) { it.tag as Song }
|
|
||||||
|
|
||||||
override val artists = vertex.artistVertices.mapTo(mutableSetOf()) { it.tag as Artist }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LibraryImpl(
|
|
||||||
override val songs: Collection<SongImpl>,
|
|
||||||
override val albums: Collection<AlbumImpl>,
|
|
||||||
override val artists: Collection<ArtistImpl>,
|
|
||||||
override val genres: Collection<GenreImpl>
|
|
||||||
) : MutableLibrary {
|
|
||||||
override val playlists = emptySet<Playlist>()
|
|
||||||
|
|
||||||
private val songUidMap = songs.associateBy { it.uid }
|
|
||||||
private val albumUidMap = albums.associateBy { it.uid }
|
|
||||||
private val artistUidMap = artists.associateBy { it.uid }
|
|
||||||
private val genreUidMap = genres.associateBy { it.uid }
|
|
||||||
private val playlistUidMap = playlists.associateBy { it.uid }
|
|
||||||
|
|
||||||
override fun findSong(uid: Music.UID) = songUidMap[uid]
|
|
||||||
|
|
||||||
override fun findSongByPath(path: Path) = songs.find { it.path == path }
|
|
||||||
|
|
||||||
override fun findAlbum(uid: Music.UID) = albumUidMap[uid]
|
|
||||||
|
|
||||||
override fun findArtist(uid: Music.UID) = artistUidMap[uid]
|
|
||||||
|
|
||||||
override fun findGenre(uid: Music.UID) = genreUidMap[uid]
|
|
||||||
|
|
||||||
override fun findPlaylist(uid: Music.UID) = playlistUidMap[uid]
|
|
||||||
|
|
||||||
override fun findPlaylistByName(name: String) = playlists.find { it.name.raw == name }
|
|
||||||
|
|
||||||
override suspend fun createPlaylist(name: String, songs: List<Song>): MutableLibrary {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun renamePlaylist(playlist: Playlist, name: String): MutableLibrary {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun addToPlaylist(playlist: Playlist, songs: List<Song>): MutableLibrary {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun rewritePlaylist(playlist: Playlist, songs: List<Song>): MutableLibrary {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun deletePlaylist(playlist: Playlist): MutableLibrary {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
}
|
|
94
app/src/main/java/org/oxycblt/musikr/model/LibraryFactory.kt
Normal file
94
app/src/main/java/org/oxycblt/musikr/model/LibraryFactory.kt
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Auxio Project
|
||||||
|
* LibraryFactory.kt is part of Auxio.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.oxycblt.musikr.model
|
||||||
|
|
||||||
|
import javax.inject.Inject
|
||||||
|
import org.oxycblt.musikr.Album
|
||||||
|
import org.oxycblt.musikr.Artist
|
||||||
|
import org.oxycblt.musikr.Genre
|
||||||
|
import org.oxycblt.musikr.MutableLibrary
|
||||||
|
import org.oxycblt.musikr.Song
|
||||||
|
import org.oxycblt.musikr.graph.AlbumVertex
|
||||||
|
import org.oxycblt.musikr.graph.ArtistVertex
|
||||||
|
import org.oxycblt.musikr.graph.GenreVertex
|
||||||
|
import org.oxycblt.musikr.graph.MusicGraph
|
||||||
|
import org.oxycblt.musikr.graph.SongVertex
|
||||||
|
|
||||||
|
interface LibraryFactory {
|
||||||
|
fun create(graph: MusicGraph): MutableLibrary
|
||||||
|
}
|
||||||
|
|
||||||
|
class LibraryFactoryImpl @Inject constructor() : LibraryFactory {
|
||||||
|
override fun create(graph: MusicGraph): MutableLibrary {
|
||||||
|
val songs =
|
||||||
|
graph.songVertex.mapTo(mutableSetOf()) { vertex ->
|
||||||
|
SongImpl(SongVertexCore(vertex)).also { vertex.tag = it }
|
||||||
|
}
|
||||||
|
val albums =
|
||||||
|
graph.albumVertex.mapTo(mutableSetOf()) { vertex ->
|
||||||
|
AlbumImpl(AlbumVertexCore(vertex)).also { vertex.tag = it }
|
||||||
|
}
|
||||||
|
val artists =
|
||||||
|
graph.artistVertex.mapTo(mutableSetOf()) { vertex ->
|
||||||
|
ArtistImpl(ArtistVertexCore(vertex)).also { vertex.tag = it }
|
||||||
|
}
|
||||||
|
val genres =
|
||||||
|
graph.genreVertex.mapTo(mutableSetOf()) { vertex ->
|
||||||
|
GenreImpl(GenreVertexCore(vertex)).also { vertex.tag = it }
|
||||||
|
}
|
||||||
|
return LibraryImpl(songs, albums, artists, genres)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SongVertexCore(private val vertex: SongVertex) : SongCore {
|
||||||
|
override val preSong = vertex.preSong
|
||||||
|
|
||||||
|
override fun resolveAlbum() = vertex.albumVertex.tag as Album
|
||||||
|
|
||||||
|
override fun resolveArtists() = vertex.artistVertices.map { it.tag as Artist }
|
||||||
|
|
||||||
|
override fun resolveGenres() = vertex.genreVertices.map { it.tag as Genre }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AlbumVertexCore(private val vertex: AlbumVertex) : AlbumCore {
|
||||||
|
override val preAlbum = vertex.preAlbum
|
||||||
|
|
||||||
|
override val songs = vertex.songVertices.map { SongImpl(SongVertexCore(it)) }
|
||||||
|
|
||||||
|
override fun resolveArtists() = vertex.artistVertices.map { it.tag as Artist }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ArtistVertexCore(private val vertex: ArtistVertex) : ArtistCore {
|
||||||
|
override val preArtist = vertex.preArtist
|
||||||
|
|
||||||
|
override val songs = vertex.songVertices.mapTo(mutableSetOf()) { it.tag as Song }
|
||||||
|
|
||||||
|
override val albums = vertex.albumVertices.mapTo(mutableSetOf()) { it.tag as Album }
|
||||||
|
|
||||||
|
override fun resolveGenres() =
|
||||||
|
vertex.genreVertices.mapTo(mutableSetOf()) { it.tag as Genre }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GenreVertexCore(vertex: GenreVertex) : GenreCore {
|
||||||
|
override val preGenre = vertex.preGenre
|
||||||
|
|
||||||
|
override val songs = vertex.songVertices.mapTo(mutableSetOf()) { it.tag as Song }
|
||||||
|
|
||||||
|
override val artists = vertex.artistVertices.mapTo(mutableSetOf()) { it.tag as Artist }
|
||||||
|
}
|
||||||
|
}
|
74
app/src/main/java/org/oxycblt/musikr/model/LibraryImpl.kt
Normal file
74
app/src/main/java/org/oxycblt/musikr/model/LibraryImpl.kt
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Auxio Project
|
||||||
|
* LibraryImpl.kt is part of Auxio.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.oxycblt.musikr.model
|
||||||
|
|
||||||
|
import org.oxycblt.musikr.Music
|
||||||
|
import org.oxycblt.musikr.MutableLibrary
|
||||||
|
import org.oxycblt.musikr.Playlist
|
||||||
|
import org.oxycblt.musikr.Song
|
||||||
|
import org.oxycblt.musikr.fs.Path
|
||||||
|
|
||||||
|
class LibraryImpl(
|
||||||
|
override val songs: Collection<SongImpl>,
|
||||||
|
override val albums: Collection<AlbumImpl>,
|
||||||
|
override val artists: Collection<ArtistImpl>,
|
||||||
|
override val genres: Collection<GenreImpl>
|
||||||
|
) : MutableLibrary {
|
||||||
|
override val playlists = emptySet<Playlist>()
|
||||||
|
|
||||||
|
private val songUidMap = songs.associateBy { it.uid }
|
||||||
|
private val albumUidMap = albums.associateBy { it.uid }
|
||||||
|
private val artistUidMap = artists.associateBy { it.uid }
|
||||||
|
private val genreUidMap = genres.associateBy { it.uid }
|
||||||
|
private val playlistUidMap = playlists.associateBy { it.uid }
|
||||||
|
|
||||||
|
override fun findSong(uid: Music.UID) = songUidMap[uid]
|
||||||
|
|
||||||
|
override fun findSongByPath(path: Path) = songs.find { it.path == path }
|
||||||
|
|
||||||
|
override fun findAlbum(uid: Music.UID) = albumUidMap[uid]
|
||||||
|
|
||||||
|
override fun findArtist(uid: Music.UID) = artistUidMap[uid]
|
||||||
|
|
||||||
|
override fun findGenre(uid: Music.UID) = genreUidMap[uid]
|
||||||
|
|
||||||
|
override fun findPlaylist(uid: Music.UID) = playlistUidMap[uid]
|
||||||
|
|
||||||
|
override fun findPlaylistByName(name: String) = playlists.find { it.name.raw == name }
|
||||||
|
|
||||||
|
override suspend fun createPlaylist(name: String, songs: List<Song>): MutableLibrary {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun renamePlaylist(playlist: Playlist, name: String): MutableLibrary {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun addToPlaylist(playlist: Playlist, songs: List<Song>): MutableLibrary {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun rewritePlaylist(playlist: Playlist, songs: List<Song>): MutableLibrary {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deletePlaylist(playlist: Playlist): MutableLibrary {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
77
app/src/main/java/org/oxycblt/musikr/model/SongImpl.kt
Normal file
77
app/src/main/java/org/oxycblt/musikr/model/SongImpl.kt
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Auxio Project
|
||||||
|
* SongImpl.kt is part of Auxio.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.oxycblt.musikr.model
|
||||||
|
|
||||||
|
import org.oxycblt.musikr.Album
|
||||||
|
import org.oxycblt.musikr.Artist
|
||||||
|
import org.oxycblt.musikr.Genre
|
||||||
|
import org.oxycblt.musikr.Song
|
||||||
|
import org.oxycblt.musikr.cover.Cover
|
||||||
|
import org.oxycblt.musikr.tag.interpret.PreSong
|
||||||
|
|
||||||
|
interface SongCore {
|
||||||
|
val preSong: PreSong
|
||||||
|
|
||||||
|
fun resolveAlbum(): Album
|
||||||
|
|
||||||
|
fun resolveArtists(): List<Artist>
|
||||||
|
|
||||||
|
fun resolveGenres(): List<Genre>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Library-backed implementation of [Song].
|
||||||
|
*
|
||||||
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
|
*/
|
||||||
|
class SongImpl(private val handle: SongCore) : Song {
|
||||||
|
private val preSong = handle.preSong
|
||||||
|
|
||||||
|
override val uid = preSong.computeUid()
|
||||||
|
override val name = preSong.name
|
||||||
|
override val track = preSong.track
|
||||||
|
override val disc = preSong.disc
|
||||||
|
override val date = preSong.date
|
||||||
|
override val uri = preSong.uri
|
||||||
|
override val path = preSong.path
|
||||||
|
override val mimeType = preSong.mimeType
|
||||||
|
override val size = preSong.size
|
||||||
|
override val durationMs = preSong.durationMs
|
||||||
|
override val replayGainAdjustment = preSong.replayGainAdjustment
|
||||||
|
override val lastModified = preSong.lastModified
|
||||||
|
override val dateAdded = preSong.dateAdded
|
||||||
|
override val cover = Cover.single(this)
|
||||||
|
override val album: Album
|
||||||
|
get() = handle.resolveAlbum()
|
||||||
|
|
||||||
|
override val artists: List<Artist>
|
||||||
|
get() = handle.resolveArtists()
|
||||||
|
|
||||||
|
override val genres: List<Genre>
|
||||||
|
get() = handle.resolveGenres()
|
||||||
|
|
||||||
|
private val hashCode = 31 * uid.hashCode() + preSong.hashCode()
|
||||||
|
|
||||||
|
override fun hashCode() = hashCode
|
||||||
|
|
||||||
|
override fun equals(other: Any?) =
|
||||||
|
other is SongImpl && uid == other.uid && preSong == other.preSong
|
||||||
|
|
||||||
|
override fun toString() = "Song(uid=$uid, name=$name)"
|
||||||
|
}
|
|
@ -26,9 +26,9 @@ import kotlinx.coroutines.flow.buffer
|
||||||
import kotlinx.coroutines.flow.filterIsInstance
|
import kotlinx.coroutines.flow.filterIsInstance
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import org.oxycblt.musikr.MutableLibrary
|
||||||
import org.oxycblt.musikr.graph.MusicGraph
|
import org.oxycblt.musikr.graph.MusicGraph
|
||||||
import org.oxycblt.musikr.model.LibraryFactory
|
import org.oxycblt.musikr.model.LibraryFactory
|
||||||
import org.oxycblt.musikr.model.MutableLibrary
|
|
||||||
import org.oxycblt.musikr.tag.Interpretation
|
import org.oxycblt.musikr.tag.Interpretation
|
||||||
import org.oxycblt.musikr.tag.interpret.TagInterpreter
|
import org.oxycblt.musikr.tag.interpret.TagInterpreter
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue