music: refactor new stack

This commit is contained in:
Alexander Capehart 2024-11-23 10:02:26 -07:00
parent 517da485e1
commit ba9ab5a445
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
47 changed files with 466 additions and 376 deletions

View file

@ -34,8 +34,8 @@ import org.oxycblt.auxio.music.info.Date
import org.oxycblt.auxio.music.info.Disc import org.oxycblt.auxio.music.info.Disc
import org.oxycblt.auxio.music.info.Name import org.oxycblt.auxio.music.info.Name
import org.oxycblt.auxio.music.info.ReleaseType import org.oxycblt.auxio.music.info.ReleaseType
import org.oxycblt.auxio.music.stack.fs.MimeType import org.oxycblt.auxio.music.stack.explore.fs.MimeType
import org.oxycblt.auxio.music.stack.fs.Path import org.oxycblt.auxio.music.stack.explore.fs.Path
import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment
import org.oxycblt.auxio.util.concatLocalized import org.oxycblt.auxio.util.concatLocalized
import org.oxycblt.auxio.util.toUuidOrNull import org.oxycblt.auxio.util.toUuidOrNull

View file

@ -24,7 +24,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject import javax.inject.Inject
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.dirs.MusicDirectories import org.oxycblt.auxio.music.dirs.MusicDirectories
import org.oxycblt.auxio.music.stack.fs.DocumentPathFactory import org.oxycblt.auxio.music.stack.explore.fs.DocumentPathFactory
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import timber.log.Timber as L import timber.log.Timber as L

View file

@ -23,7 +23,7 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemMusicDirBinding import org.oxycblt.auxio.databinding.ItemMusicDirBinding
import org.oxycblt.auxio.list.recycler.DialogRecyclerView import org.oxycblt.auxio.list.recycler.DialogRecyclerView
import org.oxycblt.auxio.music.stack.fs.Path import org.oxycblt.auxio.music.stack.explore.fs.Path
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
import timber.log.Timber as L import timber.log.Timber as L

View file

@ -18,7 +18,7 @@
package org.oxycblt.auxio.music.dirs package org.oxycblt.auxio.music.dirs
import org.oxycblt.auxio.music.stack.fs.Path import org.oxycblt.auxio.music.stack.explore.fs.Path
/** /**
* Represents the configuration for specific directories to filter to/from when loading music. * Represents the configuration for specific directories to filter to/from when loading music.

View file

@ -33,8 +33,8 @@ import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogMusicDirsBinding import org.oxycblt.auxio.databinding.DialogMusicDirsBinding
import org.oxycblt.auxio.music.MusicSettings import org.oxycblt.auxio.music.MusicSettings
import org.oxycblt.auxio.music.stack.fs.DocumentPathFactory import org.oxycblt.auxio.music.stack.explore.fs.DocumentPathFactory
import org.oxycblt.auxio.music.stack.fs.Path import org.oxycblt.auxio.music.stack.explore.fs.Path
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.showToast
import timber.log.Timber as L import timber.log.Timber as L

View file

@ -23,10 +23,10 @@ import android.net.Uri
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject import javax.inject.Inject
import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.stack.fs.Components import org.oxycblt.auxio.music.stack.explore.fs.Components
import org.oxycblt.auxio.music.stack.fs.DocumentPathFactory import org.oxycblt.auxio.music.stack.explore.fs.DocumentPathFactory
import org.oxycblt.auxio.music.stack.fs.Path import org.oxycblt.auxio.music.stack.explore.fs.Path
import org.oxycblt.auxio.music.stack.fs.contentResolverSafe import org.oxycblt.auxio.music.stack.explore.fs.contentResolverSafe
import timber.log.Timber as L import timber.log.Timber as L
/** /**

View file

@ -28,11 +28,11 @@ import java.io.OutputStream
import javax.inject.Inject import javax.inject.Inject
import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.music.stack.extractor.correctWhitespace import org.oxycblt.auxio.music.stack.explore.extractor.correctWhitespace
import org.oxycblt.auxio.music.stack.fs.Components import org.oxycblt.auxio.music.stack.explore.fs.Components
import org.oxycblt.auxio.music.stack.fs.Path import org.oxycblt.auxio.music.stack.explore.fs.Path
import org.oxycblt.auxio.music.stack.fs.Volume import org.oxycblt.auxio.music.stack.explore.fs.Volume
import org.oxycblt.auxio.music.stack.fs.VolumeManager import org.oxycblt.auxio.music.stack.explore.fs.VolumeManager
import org.oxycblt.auxio.util.unlikelyToBeNull import org.oxycblt.auxio.util.unlikelyToBeNull
import timber.log.Timber as L import timber.log.Timber as L
@ -151,7 +151,8 @@ constructor(
else -> else ->
listOf( listOf(
InterpretedPath(Components.parseUnix(path), false), InterpretedPath(Components.parseUnix(path), false),
InterpretedPath(Components.parseWindows(path), true)) InterpretedPath(Components.parseWindows(path), true)
)
} }
private fun expandInterpretation( private fun expandInterpretation(

View file

@ -24,7 +24,7 @@ import android.media.MediaFormat
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject import javax.inject.Inject
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.stack.fs.MimeType import org.oxycblt.auxio.music.stack.explore.fs.MimeType
import timber.log.Timber as L import timber.log.Timber as L
/** /**
@ -119,6 +119,7 @@ constructor(@ApplicationContext private val context: Context) : AudioProperties.
return AudioProperties( return AudioProperties(
bitrate, bitrate,
sampleRate, sampleRate,
MimeType(fromExtension = song.mimeType.fromExtension, fromFormat = formatMimeType)) MimeType(fromExtension = song.mimeType.fromExtension, fromFormat = formatMimeType)
)
} }
} }

View file

@ -18,8 +18,8 @@
package org.oxycblt.auxio.music.metadata package org.oxycblt.auxio.music.metadata
import org.oxycblt.auxio.music.stack.extractor.correctWhitespace import org.oxycblt.auxio.music.stack.explore.extractor.correctWhitespace
import org.oxycblt.auxio.music.stack.extractor.splitEscaped import org.oxycblt.auxio.music.stack.explore.extractor.splitEscaped
/** /**
* Defines the user-specified parsing of multi-value tags. This should be used to parse any tags * Defines the user-specified parsing of multi-value tags. This should be used to parse any tags

View file

@ -1,67 +0,0 @@
package org.oxycblt.auxio.music.model
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import org.oxycblt.auxio.music.stack.AudioFile
import org.oxycblt.auxio.music.stack.PlaylistFile
interface Interpreter {
suspend fun interpret(
audioFiles: Flow<AudioFile>,
playlistFiles: Flow<PlaylistFile>,
interpretation: Interpretation
): MutableLibrary
}
class LinkedSong(private val albumLinkedSong: AlbumInterpreter.LinkedSong) {
val preSong: PreSong get() = albumLinkedSong.linkedArtistSong.linkedGenreSong.preSong
val album: Linked<AlbumImpl, SongImpl> get() = albumLinkedSong.album
val artists: Linked<List<ArtistImpl>, SongImpl> get() = albumLinkedSong.linkedArtistSong.artists
val genres: Linked<List<GenreImpl>, SongImpl> get() = albumLinkedSong.linkedArtistSong.linkedGenreSong.genres
}
typealias LinkedAlbum = ArtistInterpreter.LinkedAlbum
class InterpreterImpl(
private val songInterpreter: SongInterpreter
) : Interpreter {
override suspend fun interpret(
audioFiles: Flow<AudioFile>,
playlistFiles: Flow<PlaylistFile>,
interpretation: Interpretation
): MutableLibrary {
val preSongs =
songInterpreter.prepare(audioFiles, interpretation).flowOn(Dispatchers.Main)
.buffer()
val albumInterpreter = makeAlbumTree()
val artistInterpreter = makeArtistTree()
val genreInterpreter = makeGenreTree()
val genreLinkedSongs = genreInterpreter.register(preSongs).flowOn(Dispatchers.Main).buffer()
val artistLinkedSongs =
artistInterpreter.register(genreLinkedSongs).flowOn(Dispatchers.Main).buffer()
val albumLinkedSongs =
albumInterpreter.register(artistLinkedSongs).flowOn(Dispatchers.Main)
val linkedSongs = albumLinkedSongs.map { LinkedSong(it) }.toList()
val genres = genreInterpreter.resolve()
val artists = artistInterpreter.resolve()
val albums = albumInterpreter.resolve()
val songs = linkedSongs.map { SongImpl(it) }
return LibraryImpl(songs, albums, artists, genres)
}
private fun makeAlbumTree(): AlbumInterpreter {
}
private fun makeArtistTree(): ArtistInterpreter {
}
private fun makeGenreTree(): GenreInterpreter {
}
}

View file

@ -1,44 +0,0 @@
package org.oxycblt.auxio.music.model
import kotlinx.coroutines.flow.Flow
interface AlbumInterpreter {
suspend fun register(linkedSongs: Flow<ArtistInterpreter.LinkedSong>): Flow<LinkedSong>
fun resolve(): Collection<AlbumImpl>
data class LinkedSong(
val linkedArtistSong: ArtistInterpreter.LinkedSong,
val album: Linked<AlbumImpl, SongImpl>
)
}
interface ArtistInterpreter {
suspend fun register(preSong: Flow<GenreInterpreter.LinkedSong>): Flow<LinkedSong>
fun resolve(): Collection<ArtistImpl>
data class LinkedSong(
val linkedGenreSong: GenreInterpreter.LinkedSong,
val linkedAlbum: LinkedAlbum,
val artists: Linked<List<ArtistImpl>, SongImpl>
)
data class LinkedAlbum(
val preAlbum: PreAlbum,
val artists: Linked<List<ArtistImpl>, AlbumImpl>
)
}
interface GenreInterpreter {
suspend fun register(preSong: Flow<PreSong>): Flow<LinkedSong>
fun resolve(): Collection<GenreImpl>
data class LinkedSong(
val preSong: PreSong,
val genres: Linked<List<GenreImpl>, SongImpl>
)
}
interface Linked<P, C> {
fun resolve(child: C): P
}

View file

@ -27,7 +27,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject import javax.inject.Inject
import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.MusicSettings import org.oxycblt.auxio.music.MusicSettings
import org.oxycblt.auxio.music.stack.fs.contentResolverSafe import org.oxycblt.auxio.music.stack.explore.fs.contentResolverSafe
import timber.log.Timber as L import timber.log.Timber as L
/** /**

View file

@ -20,29 +20,15 @@ package org.oxycblt.auxio.music.stack
import android.net.Uri import android.net.Uri
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import org.oxycblt.auxio.music.stack.explore.Explorer
import kotlinx.coroutines.flow.merge import org.oxycblt.auxio.music.stack.interpret.Interpretation
import kotlinx.coroutines.flow.shareIn import org.oxycblt.auxio.music.stack.interpret.Interpreter
import org.oxycblt.auxio.music.info.Name import org.oxycblt.auxio.music.stack.interpret.model.MutableLibrary
import org.oxycblt.auxio.music.metadata.Separators
import org.oxycblt.auxio.music.model.Interpretation
import org.oxycblt.auxio.music.model.Interpreter
import org.oxycblt.auxio.music.model.MutableLibrary
import org.oxycblt.auxio.music.stack.cache.TagCache
import org.oxycblt.auxio.music.stack.extractor.ExoPlayerTagExtractor
import org.oxycblt.auxio.music.stack.extractor.TagResult
import org.oxycblt.auxio.music.stack.fs.DeviceFiles
interface Indexer { interface Indexer {
suspend fun run( suspend fun run(
@ -55,37 +41,14 @@ interface Indexer {
class IndexerImpl class IndexerImpl
@Inject @Inject
constructor( constructor(
private val deviceFiles: DeviceFiles, private val explorer: Explorer,
private val tagCache: TagCache,
private val tagExtractor: ExoPlayerTagExtractor,
private val interpreter: Interpreter private val interpreter: Interpreter
) : Indexer { ) : Indexer {
override suspend fun run( override suspend fun run(
uris: List<Uri>, uris: List<Uri>,
interpretation: Interpretation interpretation: Interpretation
) = coroutineScope { ) = coroutineScope {
val deviceFiles = deviceFiles.explore(uris.asFlow()).flowOn(Dispatchers.IO).buffer() val audioFiles = explorer.explore(uris).flowOn(Dispatchers.Main).buffer()
val tagRead = tagCache.read(deviceFiles).flowOn(Dispatchers.IO).buffer() interpreter.interpret(audioFiles, emptyFlow(), interpretation)
val (cacheFiles, cacheSongs) = tagRead.results()
val tagExtractor = tagExtractor.process(cacheFiles).flowOn(Dispatchers.IO).buffer()
val (_, extractorSongs) = tagExtractor.results()
val sharedExtractorSongs =
extractorSongs.shareIn(
CoroutineScope(Dispatchers.Main),
started = SharingStarted.WhileSubscribed(),
replay = Int.MAX_VALUE)
val tagWrite =
async(Dispatchers.IO) { tagCache.write(sharedExtractorSongs) }
val library = async(Dispatchers.Main) { interpreter.interpret(
merge(cacheSongs, sharedExtractorSongs), emptyFlow(), interpretation
)}
tagWrite.await()
library.await()
}
private fun Flow<TagResult>.results(): Pair<Flow<DeviceFile>, Flow<AudioFile>> {
val files = filterIsInstance<TagResult.Miss>().map { it.file }
val songs = filterIsInstance<TagResult.Hit>().map { it.audioFile }
return files to songs
} }
} }

View file

@ -0,0 +1,14 @@
package org.oxycblt.auxio.music.stack.explore
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import org.oxycblt.auxio.music.stack.Indexer
import org.oxycblt.auxio.music.stack.IndexerImpl
@Module
@InstallIn(SingletonComponent::class)
interface ExploreModule {
@Binds fun explorer(impl: ExplorerImpl): Explorer
}

View file

@ -0,0 +1,48 @@
package org.oxycblt.auxio.music.stack.explore
import android.net.Uri
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.emptyFlow
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 kotlinx.coroutines.flow.shareIn
import org.oxycblt.auxio.music.stack.explore.cache.TagCache
import org.oxycblt.auxio.music.stack.explore.extractor.ExoPlayerTagExtractor
import org.oxycblt.auxio.music.stack.explore.extractor.TagResult
import org.oxycblt.auxio.music.stack.explore.fs.DeviceFiles
import javax.inject.Inject
interface Explorer {
fun explore(uris: List<Uri>): Flow<AudioFile>
}
class ExplorerImpl @Inject constructor(
private val deviceFiles: DeviceFiles,
private val tagCache: TagCache,
private val tagExtractor: ExoPlayerTagExtractor
) : Explorer {
override fun explore(uris: List<Uri>): Flow<AudioFile> {
val deviceFiles = deviceFiles.explore(uris.asFlow()).flowOn(Dispatchers.IO).buffer()
val tagRead = tagCache.read(deviceFiles).flowOn(Dispatchers.IO).buffer()
val (cacheFiles, cacheSongs) = tagRead.results()
val tagExtractor = tagExtractor.process(cacheFiles).flowOn(Dispatchers.IO).buffer()
val (_, extractorSongs) = tagExtractor.results()
val writtenExtractorSongs = tagCache.write(extractorSongs).flowOn(Dispatchers.IO).buffer()
return merge(cacheSongs, writtenExtractorSongs)
}
private fun Flow<TagResult>.results(): Pair<Flow<DeviceFile>, Flow<AudioFile>> {
val files = filterIsInstance<TagResult.Miss>().map { it.file }
val songs = filterIsInstance<TagResult.Hit>().map { it.audioFile }
return files to songs
}
}

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2023 Auxio Project * Copyright (c) 2023 Auxio Project
* SongInterpreter.kt is part of Auxio. * Preparer.kt is part of Auxio.
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,12 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.stack package org.oxycblt.auxio.music.stack.explore
import android.net.Uri import android.net.Uri
import org.oxycblt.auxio.music.model.SongImpl import org.oxycblt.auxio.music.stack.interpret.model.SongImpl
import org.oxycblt.auxio.music.info.Date import org.oxycblt.auxio.music.info.Date
import org.oxycblt.auxio.music.stack.fs.Path import org.oxycblt.auxio.music.stack.explore.fs.Path
data class DeviceFile( data class DeviceFile(
val uri: Uri, val uri: Uri,

View file

@ -16,19 +16,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.stack.cache package org.oxycblt.auxio.music.stack.explore.cache
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.transform import kotlinx.coroutines.flow.transform
import org.oxycblt.auxio.music.stack.AudioFile import org.oxycblt.auxio.music.stack.explore.AudioFile
import org.oxycblt.auxio.music.stack.extractor.TagResult import org.oxycblt.auxio.music.stack.explore.extractor.TagResult
import org.oxycblt.auxio.music.stack.DeviceFile import org.oxycblt.auxio.music.stack.explore.DeviceFile
interface TagCache { interface TagCache {
fun read(files: Flow<DeviceFile>): Flow<TagResult> fun read(files: Flow<DeviceFile>): Flow<TagResult>
suspend fun write(rawSongs: Flow<AudioFile>) fun write(rawSongs: Flow<AudioFile>): Flow<AudioFile>
} }
class TagCacheImpl @Inject constructor(private val tagDao: TagDao) : TagCache { class TagCacheImpl @Inject constructor(private val tagDao: TagDao) : TagCache {
@ -44,7 +45,6 @@ class TagCacheImpl @Inject constructor(private val tagDao: TagDao) : TagCache {
} }
} }
override suspend fun write(rawSongs: Flow<AudioFile>) { override fun write(rawSongs: Flow<AudioFile>) =
rawSongs.collect { rawSong -> tagDao.updateTags(Tags.fromRaw(rawSong)) } rawSongs.onEach { rawSong -> tagDao.updateTags(Tags.fromRaw(rawSong)) }
}
} }

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2023 Auxio Project * Copyright (c) 2023 Auxio Project
* CacheModule.kt is part of Auxio. * TagCacheModule.kt is part of Auxio.
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.stack.cache package org.oxycblt.auxio.music.stack.explore.cache
import android.content.Context import android.content.Context
import androidx.room.Room import androidx.room.Room

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.stack.cache package org.oxycblt.auxio.music.stack.explore.cache
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Database import androidx.room.Database
@ -28,10 +28,10 @@ import androidx.room.Query
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.TypeConverter import androidx.room.TypeConverter
import androidx.room.TypeConverters import androidx.room.TypeConverters
import org.oxycblt.auxio.music.stack.AudioFile import org.oxycblt.auxio.music.stack.explore.AudioFile
import org.oxycblt.auxio.music.info.Date import org.oxycblt.auxio.music.info.Date
import org.oxycblt.auxio.music.stack.extractor.correctWhitespace import org.oxycblt.auxio.music.stack.explore.extractor.correctWhitespace
import org.oxycblt.auxio.music.stack.extractor.splitEscaped import org.oxycblt.auxio.music.stack.explore.extractor.splitEscaped
@Database(entities = [Tags::class], version = 50, exportSchema = false) @Database(entities = [Tags::class], version = 50, exportSchema = false)
abstract class TagDatabase : RoomDatabase() { abstract class TagDatabase : RoomDatabase() {

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.stack.extractor package org.oxycblt.auxio.music.stack.explore.extractor
import android.os.HandlerThread import android.os.HandlerThread
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
@ -28,8 +28,8 @@ import javax.inject.Inject
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import org.oxycblt.auxio.music.stack.AudioFile import org.oxycblt.auxio.music.stack.explore.AudioFile
import org.oxycblt.auxio.music.stack.DeviceFile import org.oxycblt.auxio.music.stack.explore.DeviceFile
import timber.log.Timber as L import timber.log.Timber as L
interface TagResult { interface TagResult {

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.stack.extractor package org.oxycblt.auxio.music.stack.explore.extractor
import dagger.Binds import dagger.Binds
import dagger.Module import dagger.Module

View file

@ -16,12 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.stack.extractor package org.oxycblt.auxio.music.stack.explore.extractor
import androidx.core.text.isDigitsOnly import androidx.core.text.isDigitsOnly
import androidx.media3.exoplayer.MetadataRetriever import androidx.media3.exoplayer.MetadataRetriever
import javax.inject.Inject import javax.inject.Inject
import org.oxycblt.auxio.music.stack.AudioFile import org.oxycblt.auxio.music.stack.explore.AudioFile
import org.oxycblt.auxio.music.info.Date import org.oxycblt.auxio.music.info.Date
import org.oxycblt.auxio.util.nonZeroOrNull import org.oxycblt.auxio.util.nonZeroOrNull

View file

@ -0,0 +1,135 @@
/*
* Copyright (c) 2022 Auxio Project
* ID3Genre.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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.auxio.music.stack.explore.extractor
import org.oxycblt.auxio.util.positiveOrNull
/// --- GENERIC PARSING ---
// TODO: Remove the escaping checks, it's too expensive to do this for every single tag.
// TODO: I want to eventually be able to move a lot of this into TagWorker once I no longer have
// to deal with the cross-module dependencies of MediaStoreExtractor.
/**
* Split a [String] by the given selector, automatically handling escaped characters that satisfy
* the selector.
*
* @param selector A block that determines if the string should be split at a given character.
* @return One or more [String]s split by the selector.
*/
inline fun String.splitEscaped(selector: (Char) -> Boolean): List<String> {
val split = mutableListOf<String>()
var currentString = ""
var i = 0
while (i < length) {
val a = get(i)
val b = getOrNull(i + 1)
if (selector(a)) {
// Non-escaped separator, split the string here, making sure any stray whitespace
// is removed.
split.add(currentString)
currentString = ""
i++
continue
}
if (b != null && a == '\\' && selector(b)) {
// Is an escaped character, add the non-escaped variant and skip two
// characters to move on to the next one.
currentString += b
i += 2
} else {
// Non-escaped, increment normally.
currentString += a
i++
}
}
if (currentString.isNotEmpty()) {
// Had an in-progress split string that is now terminated, add it.
split.add(currentString)
}
return split
}
/**
* Fix trailing whitespace or blank contents in a [String].
*
* @return A string with trailing whitespace remove,d or null if the [String] was all whitespace or
* empty.
*/
fun String.correctWhitespace() = trim().ifBlank { null }
/**
* Fix trailing whitespace or blank contents within a list of [String]s.
*
* @return A list of non-blank strings with trailing whitespace removed.
*/
fun List<String>.correctWhitespace() = mapNotNull { it.correctWhitespace() }
/**
* Parse an ID3v2-style position + total [String] field. These fields consist of a number and an
* (optional) total value delimited by a /.
*
* @return The position value extracted from the string field, or null if:
* - The position could not be parsed
* - The position was zeroed AND the total value was not present/zeroed
*
* @see transformPositionField
*/
fun String.parseId3v2PositionField() =
split('/', limit = 2).let {
transformPositionField(it[0].toIntOrNull(), it.getOrNull(1)?.toIntOrNull())
}
/**
* Parse a vorbis-style position + total field. These fields consist of two fields for the position
* and total numbers.
*
* @param pos The position value, or null if not present.
* @param total The total value, if not present.
* @return The position value extracted from the field, or null if:
* - The position could not be parsed
* - The position was zeroed AND the total value was not present/zeroed
*
* @see transformPositionField
*/
fun parseVorbisPositionField(pos: String?, total: String?) =
transformPositionField(pos?.toIntOrNull(), total?.toIntOrNull())
/**
* Transform a raw position + total field into a position a way that tolerates placeholder values.
*
* @param pos The position value, or null if not present.
* @param total The total value, if not present.
* @return The position value extracted from the field, or null if:
* - The position could not be parsed
* - The position was zeroed AND the total value was not present/zeroed
*/
fun transformPositionField(pos: Int?, total: Int?) =
if (pos != null && (pos > 0 || (total?.positiveOrNull() != null))) {
pos
} else {
null
}

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.stack.extractor package org.oxycblt.auxio.music.stack.explore.extractor
import androidx.media3.common.Metadata import androidx.media3.common.Metadata
import androidx.media3.extractor.metadata.id3.InternalFrame import androidx.media3.extractor.metadata.id3.InternalFrame

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.stack.fs package org.oxycblt.auxio.music.stack.explore.fs
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context import android.content.Context
@ -31,7 +31,7 @@ import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.flattenMerge import kotlinx.coroutines.flow.flattenMerge
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import org.oxycblt.auxio.music.stack.DeviceFile import org.oxycblt.auxio.music.stack.explore.DeviceFile
interface DeviceFiles { interface DeviceFiles {
fun explore(uris: Flow<Uri>): Flow<DeviceFile> fun explore(uris: Flow<Uri>): Flow<DeviceFile>

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.stack.fs package org.oxycblt.auxio.music.stack.explore.fs
import android.content.ContentUris import android.content.ContentUris
import android.content.Context import android.content.Context

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.stack.fs package org.oxycblt.auxio.music.stack.explore.fs
import android.content.Context import android.content.Context
import android.media.MediaFormat import android.media.MediaFormat

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.stack.fs package org.oxycblt.auxio.music.stack.explore.fs
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context import android.content.Context

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.stack.fs package org.oxycblt.auxio.music.stack.explore.fs
import android.database.Cursor import android.database.Cursor
import android.os.Build import android.os.Build

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.stack.fs package org.oxycblt.auxio.music.stack.explore.fs
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ContentResolver import android.content.ContentResolver

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2023 Auxio Project * Copyright (c) 2023 Auxio Project
* DeviceModule.kt is part of Auxio. * InterpretModule.kt is part of Auxio.
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.model package org.oxycblt.auxio.music.stack.interpret
import dagger.Binds import dagger.Binds
import dagger.Module import dagger.Module
@ -25,8 +25,6 @@ import dagger.hilt.components.SingletonComponent
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
interface ModelModule { interface InterpretModule {
@Binds fun interpreter(factory: InterpreterImpl): Interpreter @Binds fun interpreter(factory: InterpreterImpl): Interpreter
@Binds fun preparer(preparerImpl: SongInterpreterImpl): SongInterpreter
} }

View file

@ -1,4 +1,4 @@
package org.oxycblt.auxio.music.model package org.oxycblt.auxio.music.stack.interpret
import org.oxycblt.auxio.music.info.Name import org.oxycblt.auxio.music.info.Name
import org.oxycblt.auxio.music.metadata.Separators import org.oxycblt.auxio.music.metadata.Separators

View file

@ -0,0 +1,71 @@
package org.oxycblt.auxio.music.stack.interpret
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import org.oxycblt.auxio.music.stack.explore.AudioFile
import org.oxycblt.auxio.music.stack.explore.PlaylistFile
import org.oxycblt.auxio.music.stack.interpret.linker.AlbumLinker
import org.oxycblt.auxio.music.stack.interpret.linker.ArtistLinker
import org.oxycblt.auxio.music.stack.interpret.linker.GenreLinker
import org.oxycblt.auxio.music.stack.interpret.linker.Linked
import org.oxycblt.auxio.music.stack.interpret.linker.LinkedSong
import org.oxycblt.auxio.music.stack.interpret.model.AlbumImpl
import org.oxycblt.auxio.music.stack.interpret.model.ArtistImpl
import org.oxycblt.auxio.music.stack.interpret.model.GenreImpl
import org.oxycblt.auxio.music.stack.interpret.model.LibraryImpl
import org.oxycblt.auxio.music.stack.interpret.model.MutableLibrary
import org.oxycblt.auxio.music.stack.interpret.model.SongImpl
import org.oxycblt.auxio.music.stack.interpret.prepare.PreSong
import org.oxycblt.auxio.music.stack.interpret.prepare.Preparer
interface Interpreter {
suspend fun interpret(
audioFiles: Flow<AudioFile>,
playlistFiles: Flow<PlaylistFile>,
interpretation: Interpretation
): MutableLibrary
}
class InterpreterImpl(
private val preparer: Preparer
) : Interpreter {
override suspend fun interpret(
audioFiles: Flow<AudioFile>,
playlistFiles: Flow<PlaylistFile>,
interpretation: Interpretation
): MutableLibrary {
val preSongs =
preparer.prepare(audioFiles, interpretation).flowOn(Dispatchers.Main)
.buffer()
val genreLinker = GenreLinker()
val genreLinkedSongs = genreLinker.register(preSongs).flowOn(Dispatchers.Main).buffer()
val artistLinker = ArtistLinker()
val artistLinkedSongs =
artistLinker.register(genreLinkedSongs).flowOn(Dispatchers.Main).buffer()
val albumLinker = AlbumLinker()
val albumLinkedSongs =
albumLinker.register(artistLinkedSongs)
.flowOn(Dispatchers.Main)
.map { LinkedSongImpl(it) }
.toList()
val genres = genreLinker.resolve()
val artists = artistLinker.resolve()
val albums = albumLinker.resolve()
val songs = albumLinkedSongs.map { SongImpl(it) }
return LibraryImpl(songs, albums, artists, genres)
}
private data class LinkedSongImpl(
private val albumLinkedSong: AlbumLinker.LinkedSong
) : LinkedSong {
override val preSong: PreSong get() = albumLinkedSong.linkedArtistSong.linkedGenreSong.preSong
override val album: Linked<AlbumImpl, SongImpl> get() = albumLinkedSong.album
override val artists: Linked<List<ArtistImpl>, SongImpl> get() = albumLinkedSong.linkedArtistSong.artists
override val genres: Linked<List<GenreImpl>, SongImpl> get() = albumLinkedSong.linkedArtistSong.linkedGenreSong.genres
}
}

View file

@ -0,0 +1,17 @@
package org.oxycblt.auxio.music.stack.interpret.linker
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import org.oxycblt.auxio.music.stack.interpret.model.AlbumImpl
import org.oxycblt.auxio.music.stack.interpret.model.SongImpl
class AlbumLinker {
fun register(linkedSongs: Flow<ArtistLinker.LinkedSong>): Flow<LinkedSong> = emptyFlow()
fun resolve(): Collection<AlbumImpl> = setOf()
data class LinkedSong(
val linkedArtistSong: ArtistLinker.LinkedSong,
val album: Linked<AlbumImpl, SongImpl>
)
}

View file

@ -0,0 +1,25 @@
package org.oxycblt.auxio.music.stack.interpret.linker
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import org.oxycblt.auxio.music.stack.interpret.model.AlbumImpl
import org.oxycblt.auxio.music.stack.interpret.model.ArtistImpl
import org.oxycblt.auxio.music.stack.interpret.model.SongImpl
import org.oxycblt.auxio.music.stack.interpret.prepare.PreAlbum
class ArtistLinker {
fun register(preSong: Flow<GenreLinker.LinkedSong>): Flow<LinkedSong> = emptyFlow()
fun resolve(): Collection<ArtistImpl> = setOf()
data class LinkedSong(
val linkedGenreSong: GenreLinker.LinkedSong,
val linkedAlbum: LinkedAlbum,
val artists: Linked<List<ArtistImpl>, SongImpl>
)
data class LinkedAlbum(
val preAlbum: PreAlbum,
val artists: Linked<List<ArtistImpl>, AlbumImpl>
)
}

View file

@ -0,0 +1,17 @@
package org.oxycblt.auxio.music.stack.interpret.linker
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import org.oxycblt.auxio.music.stack.interpret.model.GenreImpl
import org.oxycblt.auxio.music.stack.interpret.model.SongImpl
import org.oxycblt.auxio.music.stack.interpret.prepare.PreSong
class GenreLinker {
fun register(preSong: Flow<PreSong>): Flow<LinkedSong> = emptyFlow()
fun resolve(): Collection<GenreImpl> = setOf()
data class LinkedSong(
val preSong: PreSong,
val genres: Linked<List<GenreImpl>, SongImpl>
)
}

View file

@ -0,0 +1,24 @@
package org.oxycblt.auxio.music.stack.interpret.linker
import org.oxycblt.auxio.music.stack.interpret.model.AlbumImpl
import org.oxycblt.auxio.music.stack.interpret.model.ArtistImpl
import org.oxycblt.auxio.music.stack.interpret.model.GenreImpl
import org.oxycblt.auxio.music.stack.interpret.model.SongImpl
import org.oxycblt.auxio.music.stack.interpret.prepare.PreAlbum
import org.oxycblt.auxio.music.stack.interpret.prepare.PreSong
interface LinkedSong {
val preSong: PreSong
val album: Linked<AlbumImpl, SongImpl>
val artists: Linked<List<ArtistImpl>, SongImpl>
val genres: Linked<List<GenreImpl>, SongImpl>
}
interface LinkedAlbum {
val preAlbum: PreAlbum
val artists: Linked<List<ArtistImpl>, AlbumImpl>
}
interface Linked<P, C> {
fun resolve(child: C): P
}

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.model package org.oxycblt.auxio.music.stack.interpret.model
import org.oxycblt.auxio.image.extractor.ParentCover import org.oxycblt.auxio.image.extractor.ParentCover
import org.oxycblt.auxio.list.sort.Sort import org.oxycblt.auxio.list.sort.Sort
@ -27,6 +27,10 @@ import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.info.Date import org.oxycblt.auxio.music.info.Date
import org.oxycblt.auxio.music.stack.interpret.linker.LinkedAlbum
import org.oxycblt.auxio.music.stack.interpret.linker.LinkedSong
import org.oxycblt.auxio.music.stack.interpret.prepare.PreArtist
import org.oxycblt.auxio.music.stack.interpret.prepare.PreGenre
import org.oxycblt.auxio.util.update import org.oxycblt.auxio.util.update
import kotlin.math.min import kotlin.math.min

View file

@ -1,4 +1,4 @@
package org.oxycblt.auxio.music.model package org.oxycblt.auxio.music.stack.interpret.model
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album

View file

@ -1,139 +1,7 @@
/* package org.oxycblt.auxio.music.stack.interpret.prepare
* Copyright (c) 2022 Auxio Project
* TagUtil.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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.auxio.music.stack.extractor
import org.oxycblt.auxio.util.positiveOrNull
/// --- GENERIC PARSING ---
// TODO: Remove the escaping checks, it's too expensive to do this for every single tag.
// TODO: I want to eventually be able to move a lot of this into TagWorker once I no longer have
// to deal with the cross-module dependencies of MediaStoreExtractor.
/**
* Split a [String] by the given selector, automatically handling escaped characters that satisfy
* the selector.
*
* @param selector A block that determines if the string should be split at a given character.
* @return One or more [String]s split by the selector.
*/
inline fun String.splitEscaped(selector: (Char) -> Boolean): List<String> {
val split = mutableListOf<String>()
var currentString = ""
var i = 0
while (i < length) {
val a = get(i)
val b = getOrNull(i + 1)
if (selector(a)) {
// Non-escaped separator, split the string here, making sure any stray whitespace
// is removed.
split.add(currentString)
currentString = ""
i++
continue
}
if (b != null && a == '\\' && selector(b)) {
// Is an escaped character, add the non-escaped variant and skip two
// characters to move on to the next one.
currentString += b
i += 2
} else {
// Non-escaped, increment normally.
currentString += a
i++
}
}
if (currentString.isNotEmpty()) {
// Had an in-progress split string that is now terminated, add it.
split.add(currentString)
}
return split
}
/**
* Fix trailing whitespace or blank contents in a [String].
*
* @return A string with trailing whitespace remove,d or null if the [String] was all whitespace or
* empty.
*/
fun String.correctWhitespace() = trim().ifBlank { null }
/**
* Fix trailing whitespace or blank contents within a list of [String]s.
*
* @return A list of non-blank strings with trailing whitespace removed.
*/
fun List<String>.correctWhitespace() = mapNotNull { it.correctWhitespace() }
/// --- ID3v2 PARSING --- /// --- ID3v2 PARSING ---
/**
* Parse an ID3v2-style position + total [String] field. These fields consist of a number and an
* (optional) total value delimited by a /.
*
* @return The position value extracted from the string field, or null if:
* - The position could not be parsed
* - The position was zeroed AND the total value was not present/zeroed
*
* @see transformPositionField
*/
fun String.parseId3v2PositionField() =
split('/', limit = 2).let {
transformPositionField(it[0].toIntOrNull(), it.getOrNull(1)?.toIntOrNull())
}
/**
* Parse a vorbis-style position + total field. These fields consist of two fields for the position
* and total numbers.
*
* @param pos The position value, or null if not present.
* @param total The total value, if not present.
* @return The position value extracted from the field, or null if:
* - The position could not be parsed
* - The position was zeroed AND the total value was not present/zeroed
*
* @see transformPositionField
*/
fun parseVorbisPositionField(pos: String?, total: String?) =
transformPositionField(pos?.toIntOrNull(), total?.toIntOrNull())
/**
* Transform a raw position + total field into a position a way that tolerates placeholder values.
*
* @param pos The position value, or null if not present.
* @param total The total value, if not present.
* @return The position value extracted from the field, or null if:
* - The position could not be parsed
* - The position was zeroed AND the total value was not present/zeroed
*/
fun transformPositionField(pos: Int?, total: Int?) =
if (pos != null && (pos > 0 || (total?.positiveOrNull() != null))) {
pos
} else {
null
}
/** /**
* Parse a multi-value genre name using ID3 rules. This will convert any ID3v1 integer * Parse a multi-value genre name using ID3 rules. This will convert any ID3v1 integer
@ -170,7 +38,7 @@ private fun String.parseId3v1Genre(): String? {
// try to index the genre table with such. // try to index the genre table with such.
val numeric = val numeric =
toIntOrNull() toIntOrNull()
// Not a numeric value, try some other fixed values. // Not a numeric value, try some other fixed values.
?: return when (this) { ?: return when (this) {
// CR and RX are not technically ID3v1, but are formatted similarly to a plain // CR and RX are not technically ID3v1, but are formatted similarly to a plain
// number. // number.

View file

@ -1,4 +1,4 @@
package org.oxycblt.auxio.music.model package org.oxycblt.auxio.music.stack.interpret.prepare
import android.net.Uri import android.net.Uri
import org.oxycblt.auxio.image.extractor.Cover import org.oxycblt.auxio.image.extractor.Cover
@ -6,8 +6,8 @@ import org.oxycblt.auxio.music.info.Date
import org.oxycblt.auxio.music.info.Disc import org.oxycblt.auxio.music.info.Disc
import org.oxycblt.auxio.music.info.Name import org.oxycblt.auxio.music.info.Name
import org.oxycblt.auxio.music.info.ReleaseType import org.oxycblt.auxio.music.info.ReleaseType
import org.oxycblt.auxio.music.stack.fs.MimeType import org.oxycblt.auxio.music.stack.explore.fs.MimeType
import org.oxycblt.auxio.music.stack.fs.Path import org.oxycblt.auxio.music.stack.explore.fs.Path
import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment
import java.util.UUID import java.util.UUID

View file

@ -0,0 +1,15 @@
package org.oxycblt.auxio.music.stack.interpret.prepare
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import org.oxycblt.auxio.music.stack.interpret.Interpreter
import org.oxycblt.auxio.music.stack.interpret.InterpreterImpl
@Module
@InstallIn(SingletonComponent::class)
interface PrepareModule {
@Binds fun prepare(factory: PreparerImpl): Preparer
}

View file

@ -1,4 +1,4 @@
package org.oxycblt.auxio.music.model package org.oxycblt.auxio.music.stack.interpret.prepare
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@ -8,20 +8,20 @@ import org.oxycblt.auxio.music.info.Disc
import org.oxycblt.auxio.music.info.Name import org.oxycblt.auxio.music.info.Name
import org.oxycblt.auxio.music.info.ReleaseType import org.oxycblt.auxio.music.info.ReleaseType
import org.oxycblt.auxio.music.metadata.Separators import org.oxycblt.auxio.music.metadata.Separators
import org.oxycblt.auxio.music.stack.AudioFile import org.oxycblt.auxio.music.stack.explore.AudioFile
import org.oxycblt.auxio.music.stack.extractor.parseId3GenreNames import org.oxycblt.auxio.music.stack.explore.fs.MimeType
import org.oxycblt.auxio.music.stack.fs.MimeType import org.oxycblt.auxio.music.stack.interpret.Interpretation
import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment
import org.oxycblt.auxio.util.toUuidOrNull import org.oxycblt.auxio.util.toUuidOrNull
interface SongInterpreter { interface Preparer {
fun prepare(audioFiles: Flow<AudioFile>, interpretation: Interpretation): Flow<PreSong> fun prepare(audioFiles: Flow<AudioFile>, interpretation: Interpretation): Flow<PreSong>
} }
class SongInterpreterImpl( class PreparerImpl(
private val nameFactory: Name.Known.Factory, private val nameFactory: Name.Known.Factory,
private val separators: Separators private val separators: Separators
) : SongInterpreter { ) : Preparer {
override fun prepare(audioFiles: Flow<AudioFile>, interpretation: Interpretation) = audioFiles.map { audioFile -> override fun prepare(audioFiles: Flow<AudioFile>, interpretation: Interpretation) = audioFiles.map { audioFile ->
val individualPreArtists = makePreArtists( val individualPreArtists = makePreArtists(
audioFile.artistMusicBrainzIds, audioFile.artistMusicBrainzIds,

View file

@ -31,10 +31,10 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
import org.oxycblt.auxio.music.stack.AudioFile import org.oxycblt.auxio.music.stack.explore.AudioFile
import org.oxycblt.auxio.music.info.Date import org.oxycblt.auxio.music.info.Date
import org.oxycblt.auxio.music.stack.cache.TagDao import org.oxycblt.auxio.music.stack.explore.cache.TagDao
import org.oxycblt.auxio.music.stack.cache.Tags import org.oxycblt.auxio.music.stack.explore.cache.Tags
class CacheRepositoryTest { class CacheRepositoryTest {
@Test @Test

View file

@ -20,11 +20,11 @@ package org.oxycblt.auxio.music.metadata
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
import org.oxycblt.auxio.music.stack.extractor.correctWhitespace import org.oxycblt.auxio.music.stack.explore.extractor.correctWhitespace
import org.oxycblt.auxio.music.stack.extractor.parseId3GenreNames import org.oxycblt.auxio.music.stack.explore.extractor.parseId3GenreNames
import org.oxycblt.auxio.music.stack.extractor.parseId3v2PositionField import org.oxycblt.auxio.music.stack.explore.extractor.parseId3v2PositionField
import org.oxycblt.auxio.music.stack.extractor.parseVorbisPositionField import org.oxycblt.auxio.music.stack.explore.extractor.parseVorbisPositionField
import org.oxycblt.auxio.music.stack.extractor.splitEscaped import org.oxycblt.auxio.music.stack.explore.extractor.splitEscaped
class TagUtilTest { class TagUtilTest {
@Test @Test

View file

@ -27,7 +27,7 @@ import androidx.media3.extractor.metadata.vorbis.VorbisComment
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
import org.oxycblt.auxio.music.stack.extractor.TextTags import org.oxycblt.auxio.music.stack.explore.extractor.TextTags
class TextTagsTest { class TextTagsTest {
@Test @Test

View file

@ -26,13 +26,13 @@ import org.junit.Assert.assertNotEquals
import org.junit.Test import org.junit.Test
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.model.AlbumImpl import org.oxycblt.auxio.music.stack.interpret.model.AlbumImpl
import org.oxycblt.auxio.music.model.ArtistImpl import org.oxycblt.auxio.music.stack.interpret.model.ArtistImpl
import org.oxycblt.auxio.music.model.DeviceLibraryImpl import org.oxycblt.auxio.music.model.DeviceLibraryImpl
import org.oxycblt.auxio.music.model.GenreImpl import org.oxycblt.auxio.music.stack.interpret.model.GenreImpl
import org.oxycblt.auxio.music.model.SongImpl import org.oxycblt.auxio.music.stack.interpret.model.SongImpl
import org.oxycblt.auxio.music.stack.fs.Components import org.oxycblt.auxio.music.stack.explore.fs.Components
import org.oxycblt.auxio.music.stack.fs.Path import org.oxycblt.auxio.music.stack.explore.fs.Path
class DeviceLibraryTest { class DeviceLibraryTest {