diff --git a/app/src/main/java/org/oxycblt/auxio/music/Music.kt b/app/src/main/java/org/oxycblt/auxio/music/Music.kt index fe25c84fc..c1431a3f0 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Music.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Music.kt @@ -34,8 +34,8 @@ import org.oxycblt.auxio.music.info.Date import org.oxycblt.auxio.music.info.Disc import org.oxycblt.auxio.music.info.Name import org.oxycblt.auxio.music.info.ReleaseType -import org.oxycblt.auxio.music.stack.fs.MimeType -import org.oxycblt.auxio.music.stack.fs.Path +import org.oxycblt.auxio.music.stack.explore.fs.MimeType +import org.oxycblt.auxio.music.stack.explore.fs.Path import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment import org.oxycblt.auxio.util.concatLocalized import org.oxycblt.auxio.util.toUuidOrNull diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicSettings.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicSettings.kt index f7993a881..06b711764 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicSettings.kt @@ -24,7 +24,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import org.oxycblt.auxio.R 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 timber.log.Timber as L diff --git a/app/src/main/java/org/oxycblt/auxio/music/dirs/DirectoryAdapter.kt b/app/src/main/java/org/oxycblt/auxio/music/dirs/DirectoryAdapter.kt index f824082cb..4daa379b6 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/dirs/DirectoryAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/dirs/DirectoryAdapter.kt @@ -23,7 +23,7 @@ import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.databinding.ItemMusicDirBinding 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.inflater import timber.log.Timber as L diff --git a/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirectories.kt b/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirectories.kt index d71c81a8f..9a39d54fa 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirectories.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirectories.kt @@ -18,7 +18,7 @@ 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. diff --git a/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirsDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirsDialog.kt index f37cfc340..0c14abd65 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirsDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirsDialog.kt @@ -33,8 +33,8 @@ import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogMusicDirsBinding import org.oxycblt.auxio.music.MusicSettings -import org.oxycblt.auxio.music.stack.fs.DocumentPathFactory -import org.oxycblt.auxio.music.stack.fs.Path +import org.oxycblt.auxio.music.stack.explore.fs.DocumentPathFactory +import org.oxycblt.auxio.music.stack.explore.fs.Path import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment import org.oxycblt.auxio.util.showToast import timber.log.Timber as L diff --git a/app/src/main/java/org/oxycblt/auxio/music/external/ExternalPlaylistManager.kt b/app/src/main/java/org/oxycblt/auxio/music/external/ExternalPlaylistManager.kt index debe8be5f..0281ddb7c 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/external/ExternalPlaylistManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/external/ExternalPlaylistManager.kt @@ -23,10 +23,10 @@ import android.net.Uri import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import org.oxycblt.auxio.music.Playlist -import org.oxycblt.auxio.music.stack.fs.Components -import org.oxycblt.auxio.music.stack.fs.DocumentPathFactory -import org.oxycblt.auxio.music.stack.fs.Path -import org.oxycblt.auxio.music.stack.fs.contentResolverSafe +import org.oxycblt.auxio.music.stack.explore.fs.Components +import org.oxycblt.auxio.music.stack.explore.fs.DocumentPathFactory +import org.oxycblt.auxio.music.stack.explore.fs.Path +import org.oxycblt.auxio.music.stack.explore.fs.contentResolverSafe import timber.log.Timber as L /** diff --git a/app/src/main/java/org/oxycblt/auxio/music/external/M3U.kt b/app/src/main/java/org/oxycblt/auxio/music/external/M3U.kt index 0ce6d7df2..871852a9f 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/external/M3U.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/external/M3U.kt @@ -28,11 +28,11 @@ import java.io.OutputStream import javax.inject.Inject import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.resolveNames -import org.oxycblt.auxio.music.stack.extractor.correctWhitespace -import org.oxycblt.auxio.music.stack.fs.Components -import org.oxycblt.auxio.music.stack.fs.Path -import org.oxycblt.auxio.music.stack.fs.Volume -import org.oxycblt.auxio.music.stack.fs.VolumeManager +import org.oxycblt.auxio.music.stack.explore.extractor.correctWhitespace +import org.oxycblt.auxio.music.stack.explore.fs.Components +import org.oxycblt.auxio.music.stack.explore.fs.Path +import org.oxycblt.auxio.music.stack.explore.fs.Volume +import org.oxycblt.auxio.music.stack.explore.fs.VolumeManager import org.oxycblt.auxio.util.unlikelyToBeNull import timber.log.Timber as L @@ -151,7 +151,8 @@ constructor( else -> listOf( InterpretedPath(Components.parseUnix(path), false), - InterpretedPath(Components.parseWindows(path), true)) + InterpretedPath(Components.parseWindows(path), true) + ) } private fun expandInterpretation( diff --git a/app/src/main/java/org/oxycblt/auxio/music/metadata/AudioProperties.kt b/app/src/main/java/org/oxycblt/auxio/music/metadata/AudioProperties.kt index 20f126396..121fb38c5 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/metadata/AudioProperties.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/metadata/AudioProperties.kt @@ -24,7 +24,7 @@ import android.media.MediaFormat import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject 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 /** @@ -119,6 +119,7 @@ constructor(@ApplicationContext private val context: Context) : AudioProperties. return AudioProperties( bitrate, sampleRate, - MimeType(fromExtension = song.mimeType.fromExtension, fromFormat = formatMimeType)) + MimeType(fromExtension = song.mimeType.fromExtension, fromFormat = formatMimeType) + ) } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/metadata/Separators.kt b/app/src/main/java/org/oxycblt/auxio/music/metadata/Separators.kt index 471b0f21c..1b0401905 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/metadata/Separators.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/metadata/Separators.kt @@ -18,8 +18,8 @@ package org.oxycblt.auxio.music.metadata -import org.oxycblt.auxio.music.stack.extractor.correctWhitespace -import org.oxycblt.auxio.music.stack.extractor.splitEscaped +import org.oxycblt.auxio.music.stack.explore.extractor.correctWhitespace +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 diff --git a/app/src/main/java/org/oxycblt/auxio/music/model/Interpreter.kt b/app/src/main/java/org/oxycblt/auxio/music/model/Interpreter.kt deleted file mode 100644 index e49ba4255..000000000 --- a/app/src/main/java/org/oxycblt/auxio/music/model/Interpreter.kt +++ /dev/null @@ -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, - playlistFiles: Flow, - interpretation: Interpretation - ): MutableLibrary -} - -class LinkedSong(private val albumLinkedSong: AlbumInterpreter.LinkedSong) { - val preSong: PreSong get() = albumLinkedSong.linkedArtistSong.linkedGenreSong.preSong - val album: Linked get() = albumLinkedSong.album - val artists: Linked, SongImpl> get() = albumLinkedSong.linkedArtistSong.artists - val genres: Linked, SongImpl> get() = albumLinkedSong.linkedArtistSong.linkedGenreSong.genres -} - -typealias LinkedAlbum = ArtistInterpreter.LinkedAlbum - -class InterpreterImpl( - private val songInterpreter: SongInterpreter -) : Interpreter { - override suspend fun interpret( - audioFiles: Flow, - playlistFiles: Flow, - 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 { - } - -} diff --git a/app/src/main/java/org/oxycblt/auxio/music/model/Trees.kt b/app/src/main/java/org/oxycblt/auxio/music/model/Trees.kt deleted file mode 100644 index 8fec57fa2..000000000 --- a/app/src/main/java/org/oxycblt/auxio/music/model/Trees.kt +++ /dev/null @@ -1,44 +0,0 @@ -package org.oxycblt.auxio.music.model - -import kotlinx.coroutines.flow.Flow - - -interface AlbumInterpreter { - suspend fun register(linkedSongs: Flow): Flow - fun resolve(): Collection - - data class LinkedSong( - val linkedArtistSong: ArtistInterpreter.LinkedSong, - val album: Linked - ) -} - -interface ArtistInterpreter { - suspend fun register(preSong: Flow): Flow - fun resolve(): Collection - - data class LinkedSong( - val linkedGenreSong: GenreInterpreter.LinkedSong, - val linkedAlbum: LinkedAlbum, - val artists: Linked, SongImpl> - ) - - data class LinkedAlbum( - val preAlbum: PreAlbum, - val artists: Linked, AlbumImpl> - ) -} - -interface GenreInterpreter { - suspend fun register(preSong: Flow): Flow - fun resolve(): Collection - - data class LinkedSong( - val preSong: PreSong, - val genres: Linked, SongImpl> - ) -} - -interface Linked { - fun resolve(child: C): P -} diff --git a/app/src/main/java/org/oxycblt/auxio/music/service/SystemContentObserver.kt b/app/src/main/java/org/oxycblt/auxio/music/service/SystemContentObserver.kt index ce6cb38b7..074983291 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/service/SystemContentObserver.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/service/SystemContentObserver.kt @@ -27,7 +27,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import org.oxycblt.auxio.music.MusicRepository 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 /** diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/Indexer.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/Indexer.kt index 1fe6275a8..32071b52b 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/Indexer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/Indexer.kt @@ -20,29 +20,15 @@ package org.oxycblt.auxio.music.stack import android.net.Uri import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async 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.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.shareIn -import org.oxycblt.auxio.music.info.Name -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 +import org.oxycblt.auxio.music.stack.explore.Explorer +import org.oxycblt.auxio.music.stack.interpret.Interpretation +import org.oxycblt.auxio.music.stack.interpret.Interpreter +import org.oxycblt.auxio.music.stack.interpret.model.MutableLibrary interface Indexer { suspend fun run( @@ -55,37 +41,14 @@ interface Indexer { class IndexerImpl @Inject constructor( - private val deviceFiles: DeviceFiles, - private val tagCache: TagCache, - private val tagExtractor: ExoPlayerTagExtractor, + private val explorer: Explorer, private val interpreter: Interpreter ) : Indexer { override suspend fun run( uris: List, interpretation: Interpretation ) = coroutineScope { - 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 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.results(): Pair, Flow> { - val files = filterIsInstance().map { it.file } - val songs = filterIsInstance().map { it.audioFile } - return files to songs + val audioFiles = explorer.explore(uris).flowOn(Dispatchers.Main).buffer() + interpreter.interpret(audioFiles, emptyFlow(), interpretation) } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/explore/ExploreModule.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/ExploreModule.kt new file mode 100644 index 000000000..96ed54ee3 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/ExploreModule.kt @@ -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 +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/explore/Explorer.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/Explorer.kt new file mode 100644 index 000000000..2a96771f9 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/Explorer.kt @@ -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): Flow +} + +class ExplorerImpl @Inject constructor( + private val deviceFiles: DeviceFiles, + private val tagCache: TagCache, + private val tagExtractor: ExoPlayerTagExtractor +) : Explorer { + override fun explore(uris: List): Flow { + 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.results(): Pair, Flow> { + val files = filterIsInstance().map { it.file } + val songs = filterIsInstance().map { it.audioFile } + return files to songs + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/Files.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/Files.kt similarity index 91% rename from app/src/main/java/org/oxycblt/auxio/music/stack/Files.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/explore/Files.kt index 82e0b4eea..5008fb5c0 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/Files.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/Files.kt @@ -1,6 +1,6 @@ /* * 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 * it under the terms of the GNU General Public License as published by @@ -16,12 +16,12 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.stack +package org.oxycblt.auxio.music.stack.explore 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.stack.fs.Path +import org.oxycblt.auxio.music.stack.explore.fs.Path data class DeviceFile( val uri: Uri, diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/cache/TagCache.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/cache/TagCache.kt similarity index 75% rename from app/src/main/java/org/oxycblt/auxio/music/stack/cache/TagCache.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/explore/cache/TagCache.kt index 9ad1c2e12..56967456a 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/cache/TagCache.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/cache/TagCache.kt @@ -16,19 +16,20 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.stack.cache +package org.oxycblt.auxio.music.stack.explore.cache import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.transform -import org.oxycblt.auxio.music.stack.AudioFile -import org.oxycblt.auxio.music.stack.extractor.TagResult -import org.oxycblt.auxio.music.stack.DeviceFile +import org.oxycblt.auxio.music.stack.explore.AudioFile +import org.oxycblt.auxio.music.stack.explore.extractor.TagResult +import org.oxycblt.auxio.music.stack.explore.DeviceFile interface TagCache { fun read(files: Flow): Flow - suspend fun write(rawSongs: Flow) + fun write(rawSongs: Flow): Flow } 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) { - rawSongs.collect { rawSong -> tagDao.updateTags(Tags.fromRaw(rawSong)) } - } + override fun write(rawSongs: Flow) = + rawSongs.onEach { rawSong -> tagDao.updateTags(Tags.fromRaw(rawSong)) } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/cache/CacheModule.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/cache/TagCacheModule.kt similarity index 94% rename from app/src/main/java/org/oxycblt/auxio/music/stack/cache/CacheModule.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/explore/cache/TagCacheModule.kt index edcf5cb2f..ffb4eaf06 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/cache/CacheModule.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/cache/TagCacheModule.kt @@ -1,6 +1,6 @@ /* * 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 * it under the terms of the GNU General Public License as published by @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.stack.cache +package org.oxycblt.auxio.music.stack.explore.cache import android.content.Context import androidx.room.Room diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/cache/TagDatabase.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/cache/TagDatabase.kt similarity index 96% rename from app/src/main/java/org/oxycblt/auxio/music/stack/cache/TagDatabase.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/explore/cache/TagDatabase.kt index 1bfbefd48..84ab3c0a4 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/cache/TagDatabase.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/cache/TagDatabase.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.stack.cache +package org.oxycblt.auxio.music.stack.explore.cache import androidx.room.Dao import androidx.room.Database @@ -28,10 +28,10 @@ import androidx.room.Query import androidx.room.RoomDatabase import androidx.room.TypeConverter 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.stack.extractor.correctWhitespace -import org.oxycblt.auxio.music.stack.extractor.splitEscaped +import org.oxycblt.auxio.music.stack.explore.extractor.correctWhitespace +import org.oxycblt.auxio.music.stack.explore.extractor.splitEscaped @Database(entities = [Tags::class], version = 50, exportSchema = false) abstract class TagDatabase : RoomDatabase() { diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/extractor/ExoPlayerTagExtractor.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/extractor/ExoPlayerTagExtractor.kt similarity index 96% rename from app/src/main/java/org/oxycblt/auxio/music/stack/extractor/ExoPlayerTagExtractor.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/explore/extractor/ExoPlayerTagExtractor.kt index d1cb92c47..79e8ffbf1 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/extractor/ExoPlayerTagExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/extractor/ExoPlayerTagExtractor.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.stack.extractor +package org.oxycblt.auxio.music.stack.explore.extractor import android.os.HandlerThread import androidx.media3.common.MediaItem @@ -28,8 +28,8 @@ import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.flow -import org.oxycblt.auxio.music.stack.AudioFile -import org.oxycblt.auxio.music.stack.DeviceFile +import org.oxycblt.auxio.music.stack.explore.AudioFile +import org.oxycblt.auxio.music.stack.explore.DeviceFile import timber.log.Timber as L interface TagResult { diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/extractor/ExtractorModule.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/extractor/ExtractorModule.kt similarity index 95% rename from app/src/main/java/org/oxycblt/auxio/music/stack/extractor/ExtractorModule.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/explore/extractor/ExtractorModule.kt index 97c29e912..6ca383044 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/extractor/ExtractorModule.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/extractor/ExtractorModule.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.stack.extractor +package org.oxycblt.auxio.music.stack.explore.extractor import dagger.Binds import dagger.Module diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/extractor/TagInterpreter.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/extractor/TagInterpreter.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/music/stack/extractor/TagInterpreter.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/explore/extractor/TagInterpreter.kt index 7658ee760..8a57a9ee6 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/extractor/TagInterpreter.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/extractor/TagInterpreter.kt @@ -16,12 +16,12 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.stack.extractor +package org.oxycblt.auxio.music.stack.explore.extractor import androidx.core.text.isDigitsOnly import androidx.media3.exoplayer.MetadataRetriever 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.util.nonZeroOrNull diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/explore/extractor/TagUtil.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/extractor/TagUtil.kt new file mode 100644 index 000000000..1bbfe65a0 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/extractor/TagUtil.kt @@ -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 . + */ + +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 { + val split = mutableListOf() + 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.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 + } \ No newline at end of file diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/extractor/TextTags.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/extractor/TextTags.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/music/stack/extractor/TextTags.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/explore/extractor/TextTags.kt index dc28953b8..7a8571251 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/extractor/TextTags.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/extractor/TextTags.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.stack.extractor +package org.oxycblt.auxio.music.stack.explore.extractor import androidx.media3.common.Metadata import androidx.media3.extractor.metadata.id3.InternalFrame diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/fs/DeviceFiles.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/DeviceFiles.kt similarity index 97% rename from app/src/main/java/org/oxycblt/auxio/music/stack/fs/DeviceFiles.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/DeviceFiles.kt index d5cabaa1e..97da57fd1 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/fs/DeviceFiles.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/DeviceFiles.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.stack.fs +package org.oxycblt.auxio.music.stack.explore.fs import android.content.ContentResolver import android.content.Context @@ -31,7 +31,7 @@ import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.flattenMerge import kotlinx.coroutines.flow.flow -import org.oxycblt.auxio.music.stack.DeviceFile +import org.oxycblt.auxio.music.stack.explore.DeviceFile interface DeviceFiles { fun explore(uris: Flow): Flow diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/fs/DocumentPathFactory.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/DocumentPathFactory.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/music/stack/fs/DocumentPathFactory.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/DocumentPathFactory.kt index c0afe432f..6c4e79b29 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/fs/DocumentPathFactory.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/DocumentPathFactory.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.stack.fs +package org.oxycblt.auxio.music.stack.explore.fs import android.content.ContentUris import android.content.Context diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/fs/Fs.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/Fs.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/music/stack/fs/Fs.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/Fs.kt index 28f60d251..66ffb1256 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/fs/Fs.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/Fs.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.stack.fs +package org.oxycblt.auxio.music.stack.explore.fs import android.content.Context import android.media.MediaFormat diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/fs/FsModule.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/FsModule.kt similarity index 97% rename from app/src/main/java/org/oxycblt/auxio/music/stack/fs/FsModule.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/FsModule.kt index b45abe0c4..54906a1b3 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/fs/FsModule.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/FsModule.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.stack.fs +package org.oxycblt.auxio.music.stack.explore.fs import android.content.ContentResolver import android.content.Context diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/fs/MediaStorePathInterpreter.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/MediaStorePathInterpreter.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/music/stack/fs/MediaStorePathInterpreter.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/MediaStorePathInterpreter.kt index 0097b2f39..58d6c7b8c 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/fs/MediaStorePathInterpreter.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/MediaStorePathInterpreter.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.stack.fs +package org.oxycblt.auxio.music.stack.explore.fs import android.database.Cursor import android.os.Build diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/fs/StorageUtil.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/StorageUtil.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/music/stack/fs/StorageUtil.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/StorageUtil.kt index 7f28fa260..72767edb8 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/fs/StorageUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/explore/fs/StorageUtil.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.stack.fs +package org.oxycblt.auxio.music.stack.explore.fs import android.annotation.SuppressLint import android.content.ContentResolver diff --git a/app/src/main/java/org/oxycblt/auxio/music/model/DeviceModule.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/InterpretModule.kt similarity index 84% rename from app/src/main/java/org/oxycblt/auxio/music/model/DeviceModule.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/interpret/InterpretModule.kt index d435c200e..9443b8320 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/model/DeviceModule.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/InterpretModule.kt @@ -1,6 +1,6 @@ /* * 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 * it under the terms of the GNU General Public License as published by @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.model +package org.oxycblt.auxio.music.stack.interpret import dagger.Binds import dagger.Module @@ -25,8 +25,6 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -interface ModelModule { +interface InterpretModule { @Binds fun interpreter(factory: InterpreterImpl): Interpreter - - @Binds fun preparer(preparerImpl: SongInterpreterImpl): SongInterpreter } diff --git a/app/src/main/java/org/oxycblt/auxio/music/model/Interpretation.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/Interpretation.kt similarity index 80% rename from app/src/main/java/org/oxycblt/auxio/music/model/Interpretation.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/interpret/Interpretation.kt index 1902b9e33..96a54d2b7 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/model/Interpretation.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/Interpretation.kt @@ -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.metadata.Separators diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/Interpreter.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/Interpreter.kt new file mode 100644 index 000000000..92f9fcb0b --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/Interpreter.kt @@ -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, + playlistFiles: Flow, + interpretation: Interpretation + ): MutableLibrary +} + +class InterpreterImpl( + private val preparer: Preparer +) : Interpreter { + override suspend fun interpret( + audioFiles: Flow, + playlistFiles: Flow, + 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 get() = albumLinkedSong.album + override val artists: Linked, SongImpl> get() = albumLinkedSong.linkedArtistSong.artists + override val genres: Linked, SongImpl> get() = albumLinkedSong.linkedArtistSong.linkedGenreSong.genres + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/AlbumLinker.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/AlbumLinker.kt new file mode 100644 index 000000000..6d5d6900e --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/AlbumLinker.kt @@ -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): Flow = emptyFlow() + fun resolve(): Collection = setOf() + + data class LinkedSong( + val linkedArtistSong: ArtistLinker.LinkedSong, + val album: Linked + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/ArtistLinker.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/ArtistLinker.kt new file mode 100644 index 000000000..4cc9c4e99 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/ArtistLinker.kt @@ -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): Flow = emptyFlow() + fun resolve(): Collection = setOf() + + data class LinkedSong( + val linkedGenreSong: GenreLinker.LinkedSong, + val linkedAlbum: LinkedAlbum, + val artists: Linked, SongImpl> + ) + + data class LinkedAlbum( + val preAlbum: PreAlbum, + val artists: Linked, AlbumImpl> + ) +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/GenreLinker.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/GenreLinker.kt new file mode 100644 index 000000000..1b9e5e0cb --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/GenreLinker.kt @@ -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): Flow = emptyFlow() + fun resolve(): Collection = setOf() + + data class LinkedSong( + val preSong: PreSong, + val genres: Linked, SongImpl> + ) +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/LinkedMusic.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/LinkedMusic.kt new file mode 100644 index 000000000..cf1d38dbf --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/linker/LinkedMusic.kt @@ -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 + val artists: Linked, SongImpl> + val genres: Linked, SongImpl> +} + +interface LinkedAlbum { + val preAlbum: PreAlbum + val artists: Linked, AlbumImpl> +} + +interface Linked { + fun resolve(child: C): P +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/model/DeviceMusicImpl.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/model/DeviceMusicImpl.kt similarity index 96% rename from app/src/main/java/org/oxycblt/auxio/music/model/DeviceMusicImpl.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/interpret/model/DeviceMusicImpl.kt index 061e90e0c..60ce69f9e 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/model/DeviceMusicImpl.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/model/DeviceMusicImpl.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -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.list.sort.Sort @@ -27,6 +27,10 @@ import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.Song 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 kotlin.math.min diff --git a/app/src/main/java/org/oxycblt/auxio/music/model/Library.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/model/Library.kt similarity index 97% rename from app/src/main/java/org/oxycblt/auxio/music/model/Library.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/interpret/model/Library.kt index 759c04721..f3f3a787f 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/model/Library.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/model/Library.kt @@ -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.Album diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/extractor/TagUtil.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/prepare/ID3Genre.kt similarity index 62% rename from app/src/main/java/org/oxycblt/auxio/music/stack/extractor/TagUtil.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/interpret/prepare/ID3Genre.kt index 51400d3de..d0a0fc4df 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/stack/extractor/TagUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/prepare/ID3Genre.kt @@ -1,139 +1,7 @@ -/* - * 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 . - */ - -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 { - val split = mutableListOf() - 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.correctWhitespace() = mapNotNull { it.correctWhitespace() } +package org.oxycblt.auxio.music.stack.interpret.prepare /// --- 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 @@ -170,7 +38,7 @@ private fun String.parseId3v1Genre(): String? { // try to index the genre table with such. val numeric = toIntOrNull() - // Not a numeric value, try some other fixed values. + // Not a numeric value, try some other fixed values. ?: return when (this) { // CR and RX are not technically ID3v1, but are formatted similarly to a plain // number. diff --git a/app/src/main/java/org/oxycblt/auxio/music/model/PreMusic.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/prepare/PreMusic.kt similarity index 87% rename from app/src/main/java/org/oxycblt/auxio/music/model/PreMusic.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/interpret/prepare/PreMusic.kt index 6041dc1a3..44627ca79 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/model/PreMusic.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/prepare/PreMusic.kt @@ -1,4 +1,4 @@ -package org.oxycblt.auxio.music.model +package org.oxycblt.auxio.music.stack.interpret.prepare import android.net.Uri 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.Name import org.oxycblt.auxio.music.info.ReleaseType -import org.oxycblt.auxio.music.stack.fs.MimeType -import org.oxycblt.auxio.music.stack.fs.Path +import org.oxycblt.auxio.music.stack.explore.fs.MimeType +import org.oxycblt.auxio.music.stack.explore.fs.Path import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment import java.util.UUID diff --git a/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/prepare/PrepareModule.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/prepare/PrepareModule.kt new file mode 100644 index 000000000..ba9dd1490 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/prepare/PrepareModule.kt @@ -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 +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/model/SongInterpreter.kt b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/prepare/Preparer.kt similarity index 94% rename from app/src/main/java/org/oxycblt/auxio/music/model/SongInterpreter.kt rename to app/src/main/java/org/oxycblt/auxio/music/stack/interpret/prepare/Preparer.kt index 2198aef07..f90d8b448 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/model/SongInterpreter.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/stack/interpret/prepare/Preparer.kt @@ -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.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.ReleaseType import org.oxycblt.auxio.music.metadata.Separators -import org.oxycblt.auxio.music.stack.AudioFile -import org.oxycblt.auxio.music.stack.extractor.parseId3GenreNames -import org.oxycblt.auxio.music.stack.fs.MimeType +import org.oxycblt.auxio.music.stack.explore.AudioFile +import org.oxycblt.auxio.music.stack.explore.fs.MimeType +import org.oxycblt.auxio.music.stack.interpret.Interpretation import org.oxycblt.auxio.playback.replaygain.ReplayGainAdjustment import org.oxycblt.auxio.util.toUuidOrNull -interface SongInterpreter { +interface Preparer { fun prepare(audioFiles: Flow, interpretation: Interpretation): Flow } -class SongInterpreterImpl( +class PreparerImpl( private val nameFactory: Name.Known.Factory, private val separators: Separators -) : SongInterpreter { +) : Preparer { override fun prepare(audioFiles: Flow, interpretation: Interpretation) = audioFiles.map { audioFile -> val individualPreArtists = makePreArtists( audioFile.artistMusicBrainzIds, diff --git a/app/src/test/java/org/oxycblt/auxio/music/cache/CacheRepositoryTest.kt b/app/src/test/java/org/oxycblt/auxio/music/cache/CacheRepositoryTest.kt index 9f3545214..8ec814c93 100644 --- a/app/src/test/java/org/oxycblt/auxio/music/cache/CacheRepositoryTest.kt +++ b/app/src/test/java/org/oxycblt/auxio/music/cache/CacheRepositoryTest.kt @@ -31,10 +31,10 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue 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.stack.cache.TagDao -import org.oxycblt.auxio.music.stack.cache.Tags +import org.oxycblt.auxio.music.stack.explore.cache.TagDao +import org.oxycblt.auxio.music.stack.explore.cache.Tags class CacheRepositoryTest { @Test diff --git a/app/src/test/java/org/oxycblt/auxio/music/metadata/TagUtilTest.kt b/app/src/test/java/org/oxycblt/auxio/music/metadata/TagUtilTest.kt index 4f6b8b9ca..65a24edd2 100644 --- a/app/src/test/java/org/oxycblt/auxio/music/metadata/TagUtilTest.kt +++ b/app/src/test/java/org/oxycblt/auxio/music/metadata/TagUtilTest.kt @@ -20,11 +20,11 @@ package org.oxycblt.auxio.music.metadata import org.junit.Assert.assertEquals import org.junit.Test -import org.oxycblt.auxio.music.stack.extractor.correctWhitespace -import org.oxycblt.auxio.music.stack.extractor.parseId3GenreNames -import org.oxycblt.auxio.music.stack.extractor.parseId3v2PositionField -import org.oxycblt.auxio.music.stack.extractor.parseVorbisPositionField -import org.oxycblt.auxio.music.stack.extractor.splitEscaped +import org.oxycblt.auxio.music.stack.explore.extractor.correctWhitespace +import org.oxycblt.auxio.music.stack.explore.extractor.parseId3GenreNames +import org.oxycblt.auxio.music.stack.explore.extractor.parseId3v2PositionField +import org.oxycblt.auxio.music.stack.explore.extractor.parseVorbisPositionField +import org.oxycblt.auxio.music.stack.explore.extractor.splitEscaped class TagUtilTest { @Test diff --git a/app/src/test/java/org/oxycblt/auxio/music/metadata/TextTagsTest.kt b/app/src/test/java/org/oxycblt/auxio/music/metadata/TextTagsTest.kt index 90c571e52..c431e4cbb 100644 --- a/app/src/test/java/org/oxycblt/auxio/music/metadata/TextTagsTest.kt +++ b/app/src/test/java/org/oxycblt/auxio/music/metadata/TextTagsTest.kt @@ -27,7 +27,7 @@ import androidx.media3.extractor.metadata.vorbis.VorbisComment import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test -import org.oxycblt.auxio.music.stack.extractor.TextTags +import org.oxycblt.auxio.music.stack.explore.extractor.TextTags class TextTagsTest { @Test diff --git a/app/src/test/java/org/oxycblt/auxio/music/user/DeviceLibraryTest.kt b/app/src/test/java/org/oxycblt/auxio/music/user/DeviceLibraryTest.kt index 30dbad659..686990894 100644 --- a/app/src/test/java/org/oxycblt/auxio/music/user/DeviceLibraryTest.kt +++ b/app/src/test/java/org/oxycblt/auxio/music/user/DeviceLibraryTest.kt @@ -26,13 +26,13 @@ import org.junit.Assert.assertNotEquals import org.junit.Test import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicType -import org.oxycblt.auxio.music.model.AlbumImpl -import org.oxycblt.auxio.music.model.ArtistImpl +import org.oxycblt.auxio.music.stack.interpret.model.AlbumImpl +import org.oxycblt.auxio.music.stack.interpret.model.ArtistImpl import org.oxycblt.auxio.music.model.DeviceLibraryImpl -import org.oxycblt.auxio.music.model.GenreImpl -import org.oxycblt.auxio.music.model.SongImpl -import org.oxycblt.auxio.music.stack.fs.Components -import org.oxycblt.auxio.music.stack.fs.Path +import org.oxycblt.auxio.music.stack.interpret.model.GenreImpl +import org.oxycblt.auxio.music.stack.interpret.model.SongImpl +import org.oxycblt.auxio.music.stack.explore.fs.Components +import org.oxycblt.auxio.music.stack.explore.fs.Path class DeviceLibraryTest {