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 {
|
cpp {
|
||||||
target "*/src/**/cpp/*.cpp"
|
target "*/src/**/cpp/*.cpp"
|
||||||
clangFormat("18.1.8")
|
clangFormat("18.1.8").
|
||||||
licenseHeaderFile("NOTICE")
|
licenseHeaderFile("NOTICE")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,9 @@
|
||||||
package org.oxycblt.musikr.graph
|
package org.oxycblt.musikr.graph
|
||||||
|
|
||||||
import org.oxycblt.musikr.Music
|
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.PreAlbum
|
||||||
import org.oxycblt.musikr.tag.interpret.PreArtist
|
import org.oxycblt.musikr.tag.interpret.PreArtist
|
||||||
import org.oxycblt.musikr.tag.interpret.PreGenre
|
import org.oxycblt.musikr.tag.interpret.PreGenre
|
||||||
|
@ -34,6 +37,8 @@ internal data class MusicGraph(
|
||||||
interface Builder {
|
interface Builder {
|
||||||
fun add(preSong: PreSong)
|
fun add(preSong: PreSong)
|
||||||
|
|
||||||
|
fun add(file: PlaylistFile)
|
||||||
|
|
||||||
fun build(): MusicGraph
|
fun build(): MusicGraph
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +52,7 @@ private class MusicGraphBuilderImpl : MusicGraph.Builder {
|
||||||
private val albumVertices = mutableMapOf<PreAlbum, AlbumVertex>()
|
private val albumVertices = mutableMapOf<PreAlbum, AlbumVertex>()
|
||||||
private val artistVertices = mutableMapOf<PreArtist, ArtistVertex>()
|
private val artistVertices = mutableMapOf<PreArtist, ArtistVertex>()
|
||||||
private val genreVertices = mutableMapOf<PreGenre, GenreVertex>()
|
private val genreVertices = mutableMapOf<PreGenre, GenreVertex>()
|
||||||
|
private val playlistVertices = mutableSetOf<PlaylistVertex>()
|
||||||
|
|
||||||
override fun add(preSong: PreSong) {
|
override fun add(preSong: PreSong) {
|
||||||
val uid = preSong.computeUid()
|
val uid = preSong.computeUid()
|
||||||
|
@ -102,6 +108,10 @@ private class MusicGraphBuilderImpl : MusicGraph.Builder {
|
||||||
songVertices[uid] = songVertex
|
songVertices[uid] = songVertex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun add(file: PlaylistFile) {
|
||||||
|
playlistVertices.add(PlaylistVertex(file))
|
||||||
|
}
|
||||||
|
|
||||||
override fun build(): MusicGraph {
|
override fun build(): MusicGraph {
|
||||||
val genreClusters = genreVertices.values.groupBy { it.preGenre.rawName?.lowercase() }
|
val genreClusters = genreVertices.values.groupBy { it.preGenre.rawName?.lowercase() }
|
||||||
for (cluster in genreClusters.values) {
|
for (cluster in genreClusters.values) {
|
||||||
|
@ -124,9 +134,18 @@ private class MusicGraphBuilderImpl : MusicGraph.Builder {
|
||||||
it.artistVertices = it.artistVertices.distinct().toMutableList()
|
it.artistVertices = it.artistVertices.distinct().toMutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
songVertices.values.forEach {
|
songVertices.entries.forEach { entry ->
|
||||||
it.artistVertices = it.artistVertices.distinct().toMutableList()
|
val vertex = entry.value
|
||||||
it.genreVertices = it.genreVertices.distinct().toMutableList()
|
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(
|
return MusicGraph(
|
||||||
|
@ -318,3 +337,9 @@ internal class GenreVertex(val preGenre: PreGenre) {
|
||||||
val artistVertices = mutableSetOf<ArtistVertex>()
|
val artistVertices = mutableSetOf<ArtistVertex>()
|
||||||
var tag: Any? = null
|
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.channels.Channel
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.buffer
|
import kotlinx.coroutines.flow.buffer
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
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 kotlinx.coroutines.flow.merge
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import org.oxycblt.musikr.Interpretation
|
import org.oxycblt.musikr.Interpretation
|
||||||
import org.oxycblt.musikr.MutableLibrary
|
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.PlaylistImpl
|
||||||
|
import org.oxycblt.musikr.playlist.SongPointer
|
||||||
import org.oxycblt.musikr.tag.interpret.TagInterpreter
|
import org.oxycblt.musikr.tag.interpret.TagInterpreter
|
||||||
|
|
||||||
internal interface EvaluateStep {
|
internal interface EvaluateStep {
|
||||||
|
@ -50,14 +55,25 @@ private class EvaluateStepImpl(
|
||||||
interpretation: Interpretation,
|
interpretation: Interpretation,
|
||||||
extractedMusic: Flow<ExtractedMusic>
|
extractedMusic: Flow<ExtractedMusic>
|
||||||
): MutableLibrary {
|
): 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 =
|
val preSongs =
|
||||||
extractedMusic
|
rawSongs
|
||||||
.filterIsInstance<ExtractedMusic.Song>()
|
.map { tagInterpreter.interpret(it, interpretation) }
|
||||||
.map { tagInterpreter.interpret(it.song, interpretation) }
|
|
||||||
.flowOn(Dispatchers.Main)
|
.flowOn(Dispatchers.Main)
|
||||||
.buffer(Channel.UNLIMITED)
|
.buffer(Channel.UNLIMITED)
|
||||||
|
val playlistFiles = filterFlow.left
|
||||||
val graphBuilder = MusicGraph.builder()
|
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()
|
val graph = graphBuilder.build()
|
||||||
return libraryFactory.create(graph)
|
return libraryFactory.create(graph)
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.oxycblt.musikr.cover.Cover
|
||||||
import org.oxycblt.musikr.fs.DeviceFile
|
import org.oxycblt.musikr.fs.DeviceFile
|
||||||
import org.oxycblt.musikr.metadata.MetadataExtractor
|
import org.oxycblt.musikr.metadata.MetadataExtractor
|
||||||
import org.oxycblt.musikr.metadata.Properties
|
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.ParsedTags
|
||||||
import org.oxycblt.musikr.tag.parse.TagParser
|
import org.oxycblt.musikr.tag.parse.TagParser
|
||||||
|
|
||||||
|
@ -51,21 +52,29 @@ private class ExtractStepImpl(
|
||||||
private val tagParser: TagParser
|
private val tagParser: TagParser
|
||||||
) : ExtractStep {
|
) : ExtractStep {
|
||||||
override fun extract(storage: Storage, nodes: Flow<ExploreNode>): Flow<ExtractedMusic> {
|
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 =
|
val cacheResults =
|
||||||
nodes
|
audioNodes
|
||||||
.filterIsInstance<ExploreNode.Audio>()
|
.map { storage.cache.read(it) }
|
||||||
.map { storage.cache.read(it.file) }
|
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
.buffer(Channel.UNLIMITED)
|
.buffer(Channel.UNLIMITED)
|
||||||
val divertedFlow =
|
val cacheFlow =
|
||||||
cacheResults.divert {
|
cacheResults.divert {
|
||||||
when (it) {
|
when (it) {
|
||||||
is CacheResult.Hit -> Divert.Left(it.song)
|
is CacheResult.Hit -> Divert.Left(it.song)
|
||||||
is CacheResult.Miss -> Divert.Right(it.file)
|
is CacheResult.Miss -> Divert.Right(it.file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val cachedSongs = divertedFlow.left.map { ExtractedMusic.Song(it) }
|
val cachedSongs = cacheFlow.left.map { ExtractedMusic.Song(it) }
|
||||||
val uncachedSongs = divertedFlow.right
|
val uncachedSongs = cacheFlow.right
|
||||||
val distributedFlow = uncachedSongs.distribute(16)
|
val distributedFlow = uncachedSongs.distribute(16)
|
||||||
val extractedSongs =
|
val extractedSongs =
|
||||||
Array(distributedFlow.flows.size) { i ->
|
Array(distributedFlow.flows.size) { i ->
|
||||||
|
@ -87,8 +96,13 @@ private class ExtractStepImpl(
|
||||||
}
|
}
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
.buffer(Channel.UNLIMITED)
|
.buffer(Channel.UNLIMITED)
|
||||||
return merge<ExtractedMusic>(
|
return merge(
|
||||||
divertedFlow.manager, cachedSongs, distributedFlow.manager, writtenSongs)
|
filterFlow.manager,
|
||||||
|
cacheFlow.manager,
|
||||||
|
cachedSongs,
|
||||||
|
distributedFlow.manager,
|
||||||
|
writtenSongs,
|
||||||
|
playlistNodes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,4 +115,6 @@ data class RawSong(
|
||||||
|
|
||||||
internal sealed interface ExtractedMusic {
|
internal sealed interface ExtractedMusic {
|
||||||
data class Song(val song: RawSong) : ExtractedMusic
|
data class Song(val song: RawSong) : ExtractedMusic
|
||||||
|
|
||||||
|
data class Playlist(val file: PlaylistFile) : ExtractedMusic
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue