musikr: re-implement playlist graphing

This commit is contained in:
Alexander Capehart 2024-12-16 20:13:08 -05:00
parent a50b55cf70
commit 3d94ab67cf
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
4 changed files with 73 additions and 16 deletions

View file

@ -41,7 +41,7 @@ spotless {
cpp {
target "*/src/**/cpp/*.cpp"
clangFormat("18.1.8")
clangFormat("18.1.8").
licenseHeaderFile("NOTICE")
}
}

View file

@ -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<PreAlbum, AlbumVertex>()
private val artistVertices = mutableMapOf<PreArtist, ArtistVertex>()
private val genreVertices = mutableMapOf<PreGenre, GenreVertex>()
private val playlistVertices = mutableSetOf<PlaylistVertex>()
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<ArtistVertex>()
var tag: Any? = null
}
internal class PlaylistVertex(val file: PlaylistFile) {
val songVertices = mutableListOf<SongVertex?>()
val pointerMap = mutableMapOf<SongPointer, Int>()
val tag: Any? = null
}

View file

@ -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<ExtractedMusic>
): 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<ExtractedMusic.Song>()
.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)
}

View file

@ -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<ExploreNode>): Flow<ExtractedMusic> {
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<ExploreNode.Audio>()
.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<ExtractedMusic>(
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
}