From 3d94ab67cf31439583aa7ee924e4ec6afc2a6280 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Mon, 16 Dec 2024 20:13:08 -0500 Subject: [PATCH] musikr: re-implement playlist graphing --- build.gradle | 2 +- .../org/oxycblt/musikr/graph/MusicGraph.kt | 31 ++++++++++++++++-- .../oxycblt/musikr/pipeline/EvaluateStep.kt | 24 +++++++++++--- .../oxycblt/musikr/pipeline/ExtractStep.kt | 32 ++++++++++++++----- 4 files changed, 73 insertions(+), 16 deletions(-) diff --git a/build.gradle b/build.gradle index e5dfab5c5..464949a9e 100644 --- a/build.gradle +++ b/build.gradle @@ -41,7 +41,7 @@ spotless { cpp { target "*/src/**/cpp/*.cpp" - clangFormat("18.1.8") + clangFormat("18.1.8"). licenseHeaderFile("NOTICE") } } diff --git a/musikr/src/main/java/org/oxycblt/musikr/graph/MusicGraph.kt b/musikr/src/main/java/org/oxycblt/musikr/graph/MusicGraph.kt index 2404a3678..6e43bf092 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/graph/MusicGraph.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/graph/MusicGraph.kt @@ -19,6 +19,9 @@ package org.oxycblt.musikr.graph 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.tag.interpret.PreAlbum import org.oxycblt.musikr.tag.interpret.PreArtist import org.oxycblt.musikr.tag.interpret.PreGenre @@ -34,6 +37,8 @@ internal data class MusicGraph( interface Builder { fun add(preSong: PreSong) + fun add(file: PlaylistFile) + fun build(): MusicGraph } @@ -47,6 +52,7 @@ private class MusicGraphBuilderImpl : MusicGraph.Builder { private val albumVertices = mutableMapOf() private val artistVertices = mutableMapOf() private val genreVertices = mutableMapOf() + private val playlistVertices = mutableSetOf() override fun add(preSong: PreSong) { val uid = preSong.computeUid() @@ -102,6 +108,10 @@ private class MusicGraphBuilderImpl : MusicGraph.Builder { songVertices[uid] = songVertex } + override fun add(file: PlaylistFile) { + playlistVertices.add(PlaylistVertex(file)) + } + override fun build(): MusicGraph { val genreClusters = genreVertices.values.groupBy { it.preGenre.rawName?.lowercase() } for (cluster in genreClusters.values) { @@ -124,9 +134,18 @@ private class MusicGraphBuilderImpl : MusicGraph.Builder { it.artistVertices = it.artistVertices.distinct().toMutableList() } - songVertices.values.forEach { - it.artistVertices = it.artistVertices.distinct().toMutableList() - it.genreVertices = it.genreVertices.distinct().toMutableList() + songVertices.entries.forEach { entry -> + val vertex = entry.value + vertex.artistVertices = vertex.artistVertices.distinct().toMutableList() + vertex.genreVertices = vertex.genreVertices.distinct().toMutableList() + + playlistVertices.forEach { + val pointer = SongPointer.UID(entry.key) + val index = it.pointerMap[pointer] + if (index != null) { + it.songVertices[index] = vertex + } + } } return MusicGraph( @@ -318,3 +337,9 @@ internal class GenreVertex(val preGenre: PreGenre) { val artistVertices = mutableSetOf() var tag: Any? = null } + +internal class PlaylistVertex(val file: PlaylistFile) { + val songVertices = mutableListOf() + val pointerMap = mutableMapOf() + val tag: Any? = null +} \ No newline at end of file diff --git a/musikr/src/main/java/org/oxycblt/musikr/pipeline/EvaluateStep.kt b/musikr/src/main/java/org/oxycblt/musikr/pipeline/EvaluateStep.kt index fccee6ee9..40abdd333 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/pipeline/EvaluateStep.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/pipeline/EvaluateStep.kt @@ -22,13 +22,18 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.buffer +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach import org.oxycblt.musikr.Interpretation import org.oxycblt.musikr.MutableLibrary 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.tag.interpret.TagInterpreter internal interface EvaluateStep { @@ -50,14 +55,25 @@ private class EvaluateStepImpl( interpretation: Interpretation, extractedMusic: Flow ): MutableLibrary { + val filterFlow = extractedMusic.divert { + when (it) { + is ExtractedMusic.Song -> Divert.Right(it.song) + is ExtractedMusic.Playlist -> Divert.Left(it.file) + } + } + val rawSongs = filterFlow.right val preSongs = - extractedMusic - .filterIsInstance() - .map { tagInterpreter.interpret(it.song, interpretation) } + rawSongs + .map { tagInterpreter.interpret(it, interpretation) } .flowOn(Dispatchers.Main) .buffer(Channel.UNLIMITED) + val playlistFiles = filterFlow.left val graphBuilder = MusicGraph.builder() - preSongs.collect { graphBuilder.add(it) } + val graphBuild = merge( + preSongs.onEach { graphBuilder.add(it) }, + playlistFiles.onEach { graphBuilder.add(it) } + ) + graphBuild.collect() val graph = graphBuilder.build() return libraryFactory.create(graph) } diff --git a/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt b/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt index 520f07130..01d47a737 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt @@ -34,6 +34,7 @@ import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.fs.DeviceFile import org.oxycblt.musikr.metadata.MetadataExtractor import org.oxycblt.musikr.metadata.Properties +import org.oxycblt.musikr.playlist.PlaylistFile import org.oxycblt.musikr.tag.parse.ParsedTags import org.oxycblt.musikr.tag.parse.TagParser @@ -51,21 +52,29 @@ private class ExtractStepImpl( private val tagParser: TagParser ) : ExtractStep { override fun extract(storage: Storage, nodes: Flow): Flow { + val filterFlow = nodes.divert { + when (it) { + is ExploreNode.Audio -> Divert.Right(it.file) + is ExploreNode.Playlist -> Divert.Left(it.file) + } + } + val audioNodes = filterFlow.right + val playlistNodes = filterFlow.left.map { ExtractedMusic.Playlist(it) } + val cacheResults = - nodes - .filterIsInstance() - .map { storage.cache.read(it.file) } + audioNodes + .map { storage.cache.read(it) } .flowOn(Dispatchers.IO) .buffer(Channel.UNLIMITED) - val divertedFlow = + val cacheFlow = cacheResults.divert { when (it) { is CacheResult.Hit -> Divert.Left(it.song) is CacheResult.Miss -> Divert.Right(it.file) } } - val cachedSongs = divertedFlow.left.map { ExtractedMusic.Song(it) } - val uncachedSongs = divertedFlow.right + val cachedSongs = cacheFlow.left.map { ExtractedMusic.Song(it) } + val uncachedSongs = cacheFlow.right val distributedFlow = uncachedSongs.distribute(16) val extractedSongs = Array(distributedFlow.flows.size) { i -> @@ -87,8 +96,13 @@ private class ExtractStepImpl( } .flowOn(Dispatchers.IO) .buffer(Channel.UNLIMITED) - return merge( - divertedFlow.manager, cachedSongs, distributedFlow.manager, writtenSongs) + return merge( + filterFlow.manager, + cacheFlow.manager, + cachedSongs, + distributedFlow.manager, + writtenSongs, + playlistNodes) } } @@ -101,4 +115,6 @@ data class RawSong( internal sealed interface ExtractedMusic { data class Song(val song: RawSong) : ExtractedMusic + + data class Playlist(val file: PlaylistFile) : ExtractedMusic }