musikr: re-implement playlist graphing
This commit is contained in:
parent
a50b55cf70
commit
3d94ab67cf
4 changed files with 73 additions and 16 deletions
|
@ -41,7 +41,7 @@ spotless {
|
|||
|
||||
cpp {
|
||||
target "*/src/**/cpp/*.cpp"
|
||||
clangFormat("18.1.8")
|
||||
clangFormat("18.1.8").
|
||||
licenseHeaderFile("NOTICE")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue