music: re-add configurable covers
This commit is contained in:
parent
ff6d2fe228
commit
a1cd4f7b26
11 changed files with 194 additions and 49 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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()
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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() }
|
||||
}
|
||||
suspend fun Context.coversDir() =
|
||||
withContext(Dispatchers.IO) { filesDir.resolve("covers").apply { mkdirs() } }
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Cover>) {
|
||||
context.coversDir().listFiles()?.forEach { it.deleteRecursively() }
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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)
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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<MusicRepository.UpdateListener>()
|
||||
private val indexingListeners = mutableListOf<MusicRepository.IndexingListener>()
|
||||
@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)
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<string name="set_key_rescan" translatable="false">auxio_rescan</string>
|
||||
<string name="set_key_observing" translatable="false">auxio_observing</string>
|
||||
<string name="set_key_music_dirs" translatable="false">auxio_music_dirs</string>
|
||||
<string name="set_key_cover_mode" translatable="false">auxio_cover_mode</string>
|
||||
<string name="set_key_cover_mode" translatable="false">auxio_cover_mode2</string>
|
||||
<string name="set_key_square_covers" translatable="false">auxio_square_covers</string>
|
||||
<string name="set_key_music_dirs_include" translatable="false">auxio_include_dirs</string>
|
||||
<string name="set_key_exclude_non_music" translatable="false">auxio_exclude_non_music</string>
|
||||
|
|
Loading…
Reference in a new issue