musikr: add full playlist evaluation

This commit is contained in:
Alexander Capehart 2024-12-16 20:45:26 -05:00
parent 3d94ab67cf
commit 7fab7f7eeb
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
12 changed files with 148 additions and 30 deletions

View file

@ -18,6 +18,7 @@
package org.oxycblt.musikr
import org.oxycblt.musikr.cover.StoredCovers
import org.oxycblt.musikr.fs.Path
interface Library {
@ -27,6 +28,8 @@ interface Library {
val genres: Collection<Genre>
val playlists: Collection<Playlist>
val storedCovers: StoredCovers
fun findSong(uid: Music.UID): Song?
fun findSongByPath(path: Path): Song?

View file

@ -80,6 +80,6 @@ private class MusikrImpl(
.buffer(Channel.UNLIMITED)
.onEach { onProgress(IndexingProgress.Songs(++extractedCount, exploredCount)) }
.onCompletion { onProgress(IndexingProgress.Indeterminate) }
evaluateStep.evaluate(interpretation, extracted)
evaluateStep.evaluate(storage, interpretation, extracted)
}
}

View file

@ -22,6 +22,7 @@ import org.oxycblt.musikr.Music
import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.playlist.PlaylistFile
import org.oxycblt.musikr.playlist.SongPointer
import org.oxycblt.musikr.playlist.interpret.PrePlaylist
import org.oxycblt.musikr.tag.interpret.PreAlbum
import org.oxycblt.musikr.tag.interpret.PreArtist
import org.oxycblt.musikr.tag.interpret.PreGenre
@ -32,12 +33,13 @@ internal data class MusicGraph(
val songVertex: List<SongVertex>,
val albumVertex: List<AlbumVertex>,
val artistVertex: List<ArtistVertex>,
val genreVertex: List<GenreVertex>
val genreVertex: List<GenreVertex>,
val playlistVertex: Set<PlaylistVertex>
) {
interface Builder {
fun add(preSong: PreSong)
fun add(file: PlaylistFile)
fun add(prePlaylist: PrePlaylist)
fun build(): MusicGraph
}
@ -108,8 +110,8 @@ private class MusicGraphBuilderImpl : MusicGraph.Builder {
songVertices[uid] = songVertex
}
override fun add(file: PlaylistFile) {
playlistVertices.add(PlaylistVertex(file))
override fun add(prePlaylist: PrePlaylist) {
playlistVertices.add(PlaylistVertex(prePlaylist))
}
override fun build(): MusicGraph {
@ -152,7 +154,8 @@ private class MusicGraphBuilderImpl : MusicGraph.Builder {
songVertices.values.toList(),
albumVertices.values.toList(),
artistVertices.values.toList(),
genreVertices.values.toList())
genreVertices.values.toList(),
playlistVertices)
}
private fun simplifyGenreCluster(cluster: Collection<GenreVertex>) {
@ -338,7 +341,7 @@ internal class GenreVertex(val preGenre: PreGenre) {
var tag: Any? = null
}
internal class PlaylistVertex(val file: PlaylistFile) {
internal class PlaylistVertex(val prePlaylist: PrePlaylist) {
val songVertices = mutableListOf<SongVertex?>()
val pointerMap = mutableMapOf<SongPointer, Int>()
val tag: Any? = null

View file

@ -21,16 +21,19 @@ package org.oxycblt.musikr.model
import org.oxycblt.musikr.Album
import org.oxycblt.musikr.Artist
import org.oxycblt.musikr.Genre
import org.oxycblt.musikr.Interpretation
import org.oxycblt.musikr.MutableLibrary
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.Storage
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.PlaylistVertex
import org.oxycblt.musikr.graph.SongVertex
internal interface LibraryFactory {
fun create(graph: MusicGraph): MutableLibrary
fun create(graph: MusicGraph, storage: Storage, interpretation: Interpretation): MutableLibrary
companion object {
fun new(): LibraryFactory = LibraryFactoryImpl()
@ -38,7 +41,7 @@ internal interface LibraryFactory {
}
private class LibraryFactoryImpl() : LibraryFactory {
override fun create(graph: MusicGraph): MutableLibrary {
override fun create(graph: MusicGraph, storage: Storage, interpretation: Interpretation): MutableLibrary {
val songs =
graph.songVertex.mapTo(mutableSetOf()) { vertex ->
SongImpl(SongVertexCore(vertex)).also { vertex.tag = it }
@ -55,7 +58,11 @@ private class LibraryFactoryImpl() : LibraryFactory {
graph.genreVertex.mapTo(mutableSetOf()) { vertex ->
GenreImpl(GenreVertexCore(vertex)).also { vertex.tag = it }
}
return LibraryImpl(songs, albums, artists, genres)
val playlists =
graph.playlistVertex.mapTo(mutableSetOf()) { vertex ->
PlaylistImpl(PlaylistVertexCore(vertex))
}
return LibraryImpl(songs, albums, artists, genres, playlists, storage, interpretation)
}
private class SongVertexCore(private val vertex: SongVertex) : SongCore {
@ -94,4 +101,12 @@ private class LibraryFactoryImpl() : LibraryFactory {
override val artists = vertex.artistVertices.mapTo(mutableSetOf()) { it.tag as Artist }
}
private class PlaylistVertexCore(vertex: PlaylistVertex) : PlaylistCore {
override val prePlaylist = vertex.prePlaylist
override val songs = vertex.songVertices.mapNotNull { vertex ->
vertex?.let { it.tag as Song }
}
}
}

View file

@ -18,26 +18,35 @@
package org.oxycblt.musikr.model
import org.oxycblt.musikr.Interpretation
import org.oxycblt.musikr.Music
import org.oxycblt.musikr.MutableLibrary
import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.Storage
import org.oxycblt.musikr.cover.StoredCovers
import org.oxycblt.musikr.fs.Path
import org.oxycblt.musikr.playlist.PlaylistHandle
import org.oxycblt.musikr.playlist.db.PlaylistInfo
import org.oxycblt.musikr.playlist.db.StoredPlaylists
internal class LibraryImpl(
internal data class LibraryImpl(
override val songs: Collection<SongImpl>,
override val albums: Collection<AlbumImpl>,
override val artists: Collection<ArtistImpl>,
override val genres: Collection<GenreImpl>
override val genres: Collection<GenreImpl>,
override val playlists: Collection<Playlist>,
private val storage: Storage,
private val interpretation: Interpretation
) : 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 val storedCovers = storage.storedCovers
override fun findSong(uid: Music.UID) = songUidMap[uid]
override fun findSongByPath(path: Path) = songs.find { it.path == path }

View file

@ -22,17 +22,16 @@ import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.playlist.PlaylistHandle
import org.oxycblt.musikr.playlist.interpret.PrePlaylistInfo
import org.oxycblt.musikr.tag.Name
import org.oxycblt.musikr.tag.interpret.PrePlaylist
internal interface PlaylistCore {
val prePlaylist: PrePlaylist
val handle: PlaylistHandle
val prePlaylist: PrePlaylistInfo
val songs: List<Song>
}
internal class PlaylistImpl(private val core: PlaylistCore) : Playlist {
override val uid = core.handle.uid
override val uid = core.prePlaylist.handle.uid
override val name: Name.Known = core.prePlaylist.name
override val durationMs = core.songs.sumOf { it.durationMs }
override val cover = Cover.multi(core.songs)

View file

@ -30,28 +30,33 @@ import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import org.oxycblt.musikr.Interpretation
import org.oxycblt.musikr.MutableLibrary
import org.oxycblt.musikr.Storage
import org.oxycblt.musikr.graph.MusicGraph
import org.oxycblt.musikr.model.LibraryFactory
import org.oxycblt.musikr.model.PlaylistImpl
import org.oxycblt.musikr.playlist.SongPointer
import org.oxycblt.musikr.playlist.interpret.PlaylistInterpreter
import org.oxycblt.musikr.tag.interpret.TagInterpreter
internal interface EvaluateStep {
suspend fun evaluate(
storage: Storage,
interpretation: Interpretation,
extractedMusic: Flow<ExtractedMusic>
): MutableLibrary
companion object {
fun new(): EvaluateStep = EvaluateStepImpl(TagInterpreter.new(), LibraryFactory.new())
fun new(): EvaluateStep = EvaluateStepImpl(TagInterpreter.new(), PlaylistInterpreter.new(), LibraryFactory.new())
}
}
private class EvaluateStepImpl(
private val tagInterpreter: TagInterpreter,
private val playlistInterpreter: PlaylistInterpreter,
private val libraryFactory: LibraryFactory
) : EvaluateStep {
override suspend fun evaluate(
storage: Storage,
interpretation: Interpretation,
extractedMusic: Flow<ExtractedMusic>
): MutableLibrary {
@ -67,14 +72,17 @@ private class EvaluateStepImpl(
.map { tagInterpreter.interpret(it, interpretation) }
.flowOn(Dispatchers.Main)
.buffer(Channel.UNLIMITED)
val playlistFiles = filterFlow.left
val prePlaylists = filterFlow.left
.map { playlistInterpreter.interpret(it, interpretation) }
.flowOn(Dispatchers.Main)
.buffer(Channel.UNLIMITED)
val graphBuilder = MusicGraph.builder()
val graphBuild = merge(
preSongs.onEach { graphBuilder.add(it) },
playlistFiles.onEach { graphBuilder.add(it) }
prePlaylists.onEach { graphBuilder.add(it) }
)
graphBuild.collect()
val graph = graphBuilder.build()
return libraryFactory.create(graph)
return libraryFactory.create(graph, storage, interpretation)
}
}

View file

@ -15,13 +15,17 @@
* 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.playlist.db
import org.oxycblt.musikr.Music
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.playlist.PlaylistFile
import org.oxycblt.musikr.playlist.PlaylistHandle
import org.oxycblt.musikr.playlist.SongPointer
interface StoredPlaylists {
suspend fun new(name: String, songs: List<Song>): PlaylistHandle
suspend fun read(): List<PlaylistFile>
companion object {
@ -31,11 +35,27 @@ interface StoredPlaylists {
}
private class StoredPlaylistsImpl(private val playlistDao: PlaylistDao) : StoredPlaylists {
override suspend fun new(name: String, songs: List<Song>): PlaylistHandle {
val info =
PlaylistInfo(
Music.UID.auxio(Music.UID.Item.PLAYLIST),
name
)
playlistDao.insertPlaylist(
RawPlaylist(
info,
songs.map { PlaylistSong(it.uid) }
)
)
return StoredPlaylistHandle(info, playlistDao)
}
override suspend fun read() =
playlistDao.readRawPlaylists().map {
PlaylistFile(
it.playlistInfo.name,
it.songs.map { song -> SongPointer.UID(song.songUid) },
StoredPlaylistHandle(it.playlistInfo, playlistDao))
StoredPlaylistHandle(it.playlistInfo, playlistDao)
)
}
}

View file

@ -0,0 +1,42 @@
package org.oxycblt.musikr.playlist.interpret
import org.oxycblt.musikr.Interpretation
import org.oxycblt.musikr.playlist.PlaylistFile
import org.oxycblt.musikr.playlist.PlaylistHandle
import org.oxycblt.musikr.playlist.SongPointer
internal interface PlaylistInterpreter {
fun interpret(file: PlaylistFile, interpretation: Interpretation): PrePlaylist
fun interpret(
name: String,
handle: PlaylistHandle,
interpretation: Interpretation
): PostPlaylist
companion object {
fun new(): PlaylistInterpreter = PlaylistInterpreterImpl
}
}
private data object PlaylistInterpreterImpl : PlaylistInterpreter {
override fun interpret(file: PlaylistFile, interpretation: Interpretation) =
PrePlaylist(
name = interpretation.naming.name(file.name, null),
rawName = file.name,
handle = file.handle,
songPointers = file.songPointers
)
override fun interpret(
name: String,
handle: PlaylistHandle,
interpretation: Interpretation
): PostPlaylist =
PostPlaylist(
name = interpretation.naming.name(name, null),
rawName = name,
handle = handle
)
}

View file

@ -0,0 +1,24 @@
package org.oxycblt.musikr.playlist.interpret
import org.oxycblt.musikr.playlist.PlaylistHandle
import org.oxycblt.musikr.playlist.SongPointer
import org.oxycblt.musikr.tag.Name
internal interface PrePlaylistInfo {
val name: Name.Known
val rawName: String?
val handle: PlaylistHandle
}
internal data class PrePlaylist(
override val name: Name.Known,
override val rawName: String?,
override val handle: PlaylistHandle,
val songPointers: List<SongPointer>
) : PrePlaylistInfo
internal data class PostPlaylist(
override val name: Name.Known,
override val rawName: String?,
override val handle: PlaylistHandle,
) : PrePlaylistInfo

View file

@ -84,9 +84,3 @@ internal data class PreGenre(
val name: Name,
val rawName: String?,
)
internal data class PrePlaylist(
val name: Name.Known,
val rawName: String?,
val handle: PlaylistHandle
)

View file

@ -21,6 +21,7 @@ package org.oxycblt.musikr.tag.interpret
import org.oxycblt.musikr.Interpretation
import org.oxycblt.musikr.fs.Format
import org.oxycblt.musikr.pipeline.RawSong
import org.oxycblt.musikr.playlist.PlaylistFile
import org.oxycblt.musikr.tag.Disc
import org.oxycblt.musikr.tag.Name
import org.oxycblt.musikr.tag.Placeholder