From a1cd4f7b26a6f5dc1d7f5221784e0b62d4efd2ba Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Sat, 28 Dec 2024 14:00:40 -0500 Subject: [PATCH] music: re-add configurable covers --- .../java/org/oxycblt/auxio/IntegerTable.kt | 8 ++-- .../java/org/oxycblt/auxio/image/CoverMode.kt | 18 +++---- .../org/oxycblt/auxio/image/ImageSettings.kt | 25 ++++++++-- .../oxycblt/auxio/image/covers/CoverModule.kt | 38 +++++++++++++++ .../oxycblt/auxio/image/covers/CoverUtil.kt | 23 +++++++-- .../oxycblt/auxio/image/covers/NullCovers.kt | 28 ++++++++--- .../auxio/image/covers/SettingCovers.kt | 48 +++++++++++++++++++ .../auxio/image/covers/SiloedCovers.kt | 6 ++- .../oxycblt/auxio/music/MusicRepository.kt | 32 +++++-------- .../categories/MusicPreferenceFragment.kt | 15 +++++- app/src/main/res/values/settings.xml | 2 +- 11 files changed, 194 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/image/covers/CoverModule.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/image/covers/SettingCovers.kt diff --git a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt index e33205029..1da5824b9 100644 --- a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt +++ b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt @@ -123,10 +123,10 @@ object IntegerTable { const val ACTION_MODE_SHUFFLE = 0xA11B /** CoverMode.Off */ const val COVER_MODE_OFF = 0xA11C - /** CoverMode.MediaStore */ - const val COVER_MODE_FAST = 0xA11D + /** CoverMode.Balanced */ + const val COVER_MODE_BALANCED = 0xA11D /** CoverMode.Quality */ - const val COVER_MODE_QUALITY = 0xA11E + const val COVER_MODE_HIGH_QUALITY = 0xA11E /** PlaySong.FromAll */ const val PLAY_SONG_FROM_ALL = 0xA11F /** PlaySong.FromAlbum */ @@ -139,4 +139,6 @@ object IntegerTable { const val PLAY_SONG_FROM_PLAYLIST = 0xA123 /** PlaySong.ByItself */ const val PLAY_SONG_BY_ITSELF = 0xA124 + /** CoverMode.SaveSpace */ + const val COVER_MODE_SAVE_SPACE = 0xA125 } diff --git a/app/src/main/java/org/oxycblt/auxio/image/CoverMode.kt b/app/src/main/java/org/oxycblt/auxio/image/CoverMode.kt index e28ddf55b..cad4a2ab2 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/CoverMode.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/CoverMode.kt @@ -26,12 +26,10 @@ import org.oxycblt.auxio.IntegerTable * @author Alexander Capehart (OxygenCobalt) */ enum class CoverMode { - /** Do not load album covers ("Off"). */ OFF, - /** Load covers from the fast, but lower-quality media store database ("Fast"). */ - FAST, - /** Load high-quality covers directly from music files ("Quality"). */ - QUALITY; + SAVE_SPACE, + BALANCED, + HIGH_QUALITY; /** * The integer representation of this instance. @@ -42,8 +40,9 @@ enum class CoverMode { get() = when (this) { OFF -> IntegerTable.COVER_MODE_OFF - FAST -> IntegerTable.COVER_MODE_FAST - QUALITY -> IntegerTable.COVER_MODE_QUALITY + SAVE_SPACE -> IntegerTable.COVER_MODE_SAVE_SPACE + BALANCED -> IntegerTable.COVER_MODE_BALANCED + HIGH_QUALITY -> IntegerTable.COVER_MODE_HIGH_QUALITY } companion object { @@ -57,8 +56,9 @@ enum class CoverMode { fun fromIntCode(intCode: Int) = when (intCode) { IntegerTable.COVER_MODE_OFF -> OFF - IntegerTable.COVER_MODE_FAST -> FAST - IntegerTable.COVER_MODE_QUALITY -> QUALITY + IntegerTable.COVER_MODE_SAVE_SPACE -> SAVE_SPACE + IntegerTable.COVER_MODE_BALANCED -> BALANCED + IntegerTable.COVER_MODE_HIGH_QUALITY -> HIGH_QUALITY else -> null } } diff --git a/app/src/main/java/org/oxycblt/auxio/image/ImageSettings.kt b/app/src/main/java/org/oxycblt/auxio/image/ImageSettings.kt index 1ebbcf7b5..12671f822 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/ImageSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/ImageSettings.kt @@ -49,7 +49,7 @@ class ImageSettingsImpl @Inject constructor(@ApplicationContext context: Context get() = CoverMode.fromIntCode( sharedPreferences.getInt(getString(R.string.set_key_cover_mode), Int.MIN_VALUE)) - ?: CoverMode.FAST + ?: CoverMode.BALANCED override val forceSquareCovers: Boolean get() = sharedPreferences.getBoolean(getString(R.string.set_key_square_covers), false) @@ -64,8 +64,8 @@ class ImageSettingsImpl @Inject constructor(@ApplicationContext context: Context when { !sharedPreferences.getBoolean(OLD_KEY_SHOW_COVERS, true) -> CoverMode.OFF !sharedPreferences.getBoolean(OLD_KEY_QUALITY_COVERS, true) -> - CoverMode.FAST - else -> CoverMode.QUALITY + CoverMode.BALANCED + else -> CoverMode.BALANCED } sharedPreferences.edit { @@ -74,6 +74,24 @@ class ImageSettingsImpl @Inject constructor(@ApplicationContext context: Context remove(OLD_KEY_QUALITY_COVERS) } } + + if (sharedPreferences.contains(OLD_KEY_COVER_MODE)) { + L.d("Migrating cover mode setting") + + var mode = + CoverMode.fromIntCode(sharedPreferences.getInt(OLD_KEY_COVER_MODE, Int.MIN_VALUE)) + ?: CoverMode.BALANCED + if (mode == CoverMode.HIGH_QUALITY) { + // High quality now has space characteristics that could be + // undesirable, clamp to balanced. + mode = CoverMode.BALANCED + } + + sharedPreferences.edit { + putInt(getString(R.string.set_key_cover_mode), mode.intCode) + remove(OLD_KEY_COVER_MODE) + } + } } override fun onSettingChanged(key: String, listener: ImageSettings.Listener) { @@ -87,5 +105,6 @@ class ImageSettingsImpl @Inject constructor(@ApplicationContext context: Context private companion object { const val OLD_KEY_SHOW_COVERS = "KEY_SHOW_COVERS" const val OLD_KEY_QUALITY_COVERS = "KEY_QUALITY_COVERS" + const val OLD_KEY_COVER_MODE = "auxio_cover_mode" } } diff --git a/app/src/main/java/org/oxycblt/auxio/image/covers/CoverModule.kt b/app/src/main/java/org/oxycblt/auxio/image/covers/CoverModule.kt new file mode 100644 index 000000000..860ca10c5 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/image/covers/CoverModule.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 Auxio Project + * CoverModule.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.image.covers + +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.oxycblt.musikr.cover.CoverIdentifier + +@Module +@InstallIn(SingletonComponent::class) +abstract class CoverModule { + @Binds abstract fun configCovers(impl: SettingCoversImpl): SettingCovers +} + +@Module +@InstallIn(SingletonComponent::class) +abstract class CoverProvidesModule { + @Provides fun identifier(): CoverIdentifier = CoverIdentifier.md5() +} diff --git a/app/src/main/java/org/oxycblt/auxio/image/covers/CoverUtil.kt b/app/src/main/java/org/oxycblt/auxio/image/covers/CoverUtil.kt index e556124dc..67058e8b6 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/covers/CoverUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/covers/CoverUtil.kt @@ -1,9 +1,26 @@ +/* + * Copyright (c) 2024 Auxio Project + * CoverUtil.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.image.covers import android.content.Context import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -suspend fun Context.coversDir() = withContext(Dispatchers.IO) { - filesDir.resolve("covers").apply { mkdirs() } -} \ No newline at end of file +suspend fun Context.coversDir() = + withContext(Dispatchers.IO) { filesDir.resolve("covers").apply { mkdirs() } } diff --git a/app/src/main/java/org/oxycblt/auxio/image/covers/NullCovers.kt b/app/src/main/java/org/oxycblt/auxio/image/covers/NullCovers.kt index b97d274f2..d88bbd4ac 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/covers/NullCovers.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/covers/NullCovers.kt @@ -1,18 +1,34 @@ +/* + * Copyright (c) 2024 Auxio Project + * NullCovers.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.image.covers import android.content.Context import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.cover.CoverIdentifier -import org.oxycblt.musikr.cover.Covers import org.oxycblt.musikr.cover.MutableCovers import org.oxycblt.musikr.cover.ObtainResult -import java.io.InputStream -class NullCovers(private val context: Context, private val identifier: CoverIdentifier) : MutableCovers { +class NullCovers(private val context: Context, private val identifier: CoverIdentifier) : + MutableCovers { override suspend fun obtain(id: String) = ObtainResult.Hit(NullCover(id)) - override suspend fun write(data: ByteArray): Cover = - NullCover(identifier.identify(data)) + override suspend fun write(data: ByteArray): Cover = NullCover(identifier.identify(data)) override suspend fun cleanup(excluding: Collection) { context.coversDir().listFiles()?.forEach { it.deleteRecursively() } @@ -21,4 +37,4 @@ class NullCovers(private val context: Context, private val identifier: CoverIden private class NullCover(override val id: String) : Cover { override suspend fun open() = null -} \ No newline at end of file +} diff --git a/app/src/main/java/org/oxycblt/auxio/image/covers/SettingCovers.kt b/app/src/main/java/org/oxycblt/auxio/image/covers/SettingCovers.kt new file mode 100644 index 000000000..eb3603175 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/image/covers/SettingCovers.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 Auxio Project + * SettingCovers.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.image.covers + +import android.content.Context +import java.util.UUID +import javax.inject.Inject +import org.oxycblt.auxio.image.CoverMode +import org.oxycblt.auxio.image.ImageSettings +import org.oxycblt.musikr.cover.CoverIdentifier +import org.oxycblt.musikr.cover.CoverParams +import org.oxycblt.musikr.cover.MutableCovers + +interface SettingCovers { + suspend fun create(context: Context, revision: UUID): MutableCovers +} + +class SettingCoversImpl +@Inject +constructor(private val imageSettings: ImageSettings, private val identifier: CoverIdentifier) : + SettingCovers { + override suspend fun create(context: Context, revision: UUID): MutableCovers = + when (imageSettings.coverMode) { + CoverMode.OFF -> NullCovers(context, identifier) + CoverMode.SAVE_SPACE -> siloedCovers(context, revision, CoverParams.of(750, 70)) + CoverMode.BALANCED -> siloedCovers(context, revision, CoverParams.of(750, 85)) + CoverMode.HIGH_QUALITY -> siloedCovers(context, revision, CoverParams.of(1000, 100)) + } + + private suspend fun siloedCovers(context: Context, revision: UUID, with: CoverParams) = + SiloedCovers.from(context, CoverSilo(revision, with), identifier) +} diff --git a/app/src/main/java/org/oxycblt/auxio/image/covers/SiloedCovers.kt b/app/src/main/java/org/oxycblt/auxio/image/covers/SiloedCovers.kt index 2448845ed..8a9949835 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/covers/SiloedCovers.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/covers/SiloedCovers.kt @@ -57,7 +57,11 @@ class SiloedCovers( } companion object { - suspend fun from(context: Context, silo: CoverSilo, identifier: CoverIdentifier): SiloedCovers { + suspend fun from( + context: Context, + silo: CoverSilo, + identifier: CoverIdentifier + ): SiloedCovers { val rootDir: File val revisionDir: File withContext(Dispatchers.IO) { diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt index 8fc2c9839..86cbf834e 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package org.oxycblt.auxio.music import android.content.Context @@ -27,9 +27,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.withContext import kotlinx.coroutines.yield +import org.oxycblt.auxio.image.covers.SettingCovers import org.oxycblt.auxio.music.MusicRepository.IndexingWorker -import org.oxycblt.auxio.image.covers.CoverSilo -import org.oxycblt.auxio.image.covers.SiloedCovers import org.oxycblt.musikr.IndexingProgress import org.oxycblt.musikr.Interpretation import org.oxycblt.musikr.Library @@ -40,8 +39,6 @@ import org.oxycblt.musikr.Playlist import org.oxycblt.musikr.Song import org.oxycblt.musikr.Storage import org.oxycblt.musikr.cache.StoredCache -import org.oxycblt.musikr.cover.CoverIdentifier -import org.oxycblt.musikr.cover.CoverParams import org.oxycblt.musikr.playlist.db.StoredPlaylists import org.oxycblt.musikr.tag.interpret.Naming import org.oxycblt.musikr.tag.interpret.Separators @@ -241,19 +238,16 @@ constructor( @ApplicationContext private val context: Context, private val storedCache: StoredCache, private val storedPlaylists: StoredPlaylists, + private val settingCovers: SettingCovers, private val musicSettings: MusicSettings ) : MusicRepository { private val updateListeners = mutableListOf() private val indexingListeners = mutableListOf() - @Volatile - private var indexingWorker: MusicRepository.IndexingWorker? = null + @Volatile private var indexingWorker: MusicRepository.IndexingWorker? = null - @Volatile - override var library: MutableLibrary? = null - @Volatile - private var previousCompletedState: IndexingState.Completed? = null - @Volatile - private var currentIndexingState: IndexingState? = null + @Volatile override var library: MutableLibrary? = null + @Volatile private var previousCompletedState: IndexingState.Completed? = null + @Volatile private var currentIndexingState: IndexingState? = null override val indexingState: IndexingState? get() = currentIndexingState ?: previousCompletedState @@ -393,11 +387,7 @@ constructor( val currentRevision = musicSettings.revision val newRevision = currentRevision?.takeIf { withCache } ?: UUID.randomUUID() val cache = if (withCache) storedCache.visible() else storedCache.invisible() - val covers = SiloedCovers.from( - context, - CoverSilo(newRevision, CoverParams.of(750, 80)), - CoverIdentifier.md5() - ) + val covers = settingCovers.create(context, newRevision) val storage = Storage(cache, covers, storedPlaylists) val interpretation = Interpretation(nameFactory, separators) @@ -423,9 +413,9 @@ constructor( // TODO: Remove this once you start work on kindred. deviceLibraryChanged = this.library?.songs != newLibrary.songs || - this.library?.albums != newLibrary.albums || - this.library?.artists != newLibrary.artists || - this.library?.genres != newLibrary.genres + this.library?.albums != newLibrary.albums || + this.library?.artists != newLibrary.artists || + this.library?.genres != newLibrary.genres userLibraryChanged = this.library?.playlists != newLibrary.playlists if (!deviceLibraryChanged && !userLibraryChanged) { L.d("Library has not changed, skipping update") diff --git a/app/src/main/java/org/oxycblt/auxio/settings/categories/MusicPreferenceFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/categories/MusicPreferenceFragment.kt index c98d3b55c..8727aba3d 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/categories/MusicPreferenceFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/categories/MusicPreferenceFragment.kt @@ -18,12 +18,14 @@ package org.oxycblt.auxio.settings.categories +import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.preference.Preference import coil3.ImageLoader import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject import org.oxycblt.auxio.R +import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.settings.BasePreferenceFragment import org.oxycblt.auxio.settings.ui.WrappedDialogPreference import org.oxycblt.auxio.util.navigateSafe @@ -36,6 +38,7 @@ import timber.log.Timber as L */ @AndroidEntryPoint class MusicPreferenceFragment : BasePreferenceFragment(R.xml.preferences_music) { + private val musicModel: MusicViewModel by viewModels() @Inject lateinit var imageLoader: ImageLoader override fun onOpenDialogPreference(preference: WrappedDialogPreference) { @@ -46,9 +49,17 @@ class MusicPreferenceFragment : BasePreferenceFragment(R.xml.preferences_music) } override fun onSetupPreference(preference: Preference) { - if (preference.key == getString(R.string.set_key_cover_mode) || - preference.key == getString(R.string.set_key_square_covers)) { + if (preference.key == getString(R.string.set_key_cover_mode)) { L.d("Configuring cover mode setting") + preference.onPreferenceChangeListener = + Preference.OnPreferenceChangeListener { _, _ -> + L.d("Cover mode changed, reloading music") + musicModel.refresh() + true + } + } + if (preference.key == getString(R.string.set_key_square_covers)) { + L.d("Configuring square cover setting") preference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ -> L.d("Cover mode changed, resetting image memory cache") diff --git a/app/src/main/res/values/settings.xml b/app/src/main/res/values/settings.xml index a3502babb..cf397237a 100644 --- a/app/src/main/res/values/settings.xml +++ b/app/src/main/res/values/settings.xml @@ -14,7 +14,7 @@ auxio_rescan auxio_observing auxio_music_dirs - auxio_cover_mode + auxio_cover_mode2 auxio_square_covers auxio_include_dirs auxio_exclude_non_music