image: implement compat covers backport

For cover.jpg users
This commit is contained in:
Alexander Capehart 2025-02-26 16:04:40 -07:00
parent 25901a0f76
commit 7906867a96
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
7 changed files with 107 additions and 19 deletions

View file

@ -25,13 +25,19 @@ import android.content.UriMatcher
import android.database.Cursor
import android.net.Uri
import android.os.ParcelFileDescriptor
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.runBlocking
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.image.covers.SettingCovers
import org.oxycblt.auxio.image.covers.SiloedCoverId
import org.oxycblt.auxio.image.covers.SiloedCovers
import org.oxycblt.musikr.cover.CoverResult
import javax.inject.Inject
class CoverProvider : ContentProvider() {
@AndroidEntryPoint
class CoverProvider @Inject constructor(
private val settingCovers: SettingCovers
) : ContentProvider() {
override fun onCreate(): Boolean = true
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
@ -39,12 +45,10 @@ class CoverProvider : ContentProvider() {
return null
}
val id = uri.lastPathSegment ?: return null
val coverId = SiloedCoverId.parse(id) ?: return null
return runBlocking {
val siloedCovers = SiloedCovers.from(requireNotNull(context), coverId.silo)
when (val res = siloedCovers.obtain(id)) {
is CoverResult.Hit -> res.cover.fd()
is CoverResult.Miss -> null
when (val result = settingCovers.obtain(requireNotNull(context), id)) {
is CoverResult.Hit -> result.cover.fd()
else -> null
}
}
}

View file

@ -0,0 +1,72 @@
package org.oxycblt.auxio.image.covers
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.ParcelFileDescriptor
import android.provider.MediaStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.cover.CoverResult
import org.oxycblt.musikr.cover.Covers
import org.oxycblt.musikr.cover.FileCover
import org.oxycblt.musikr.cover.MutableCovers
import org.oxycblt.musikr.fs.DeviceFile
import org.oxycblt.musikr.metadata.Metadata
open class CompatCovers(private val context: Context, private val inner: Covers<FileCover>) : Covers<FileCover> {
override suspend fun obtain(id: String): CoverResult<FileCover> {
when (val innerResult = inner.obtain(id)) {
is CoverResult.Hit -> return CoverResult.Hit(innerResult.cover)
is CoverResult.Miss -> {
if (!id.startsWith("compat:")) return CoverResult.Miss()
val uri = Uri.parse(id.substringAfter("compat:"))
return CoverResult.Hit(CompatCover(context, uri))
}
}
}
}
class MutableCompatCovers(private val context: Context, private val inner: MutableCovers<FileCover>) : CompatCovers(context, inner), MutableCovers<FileCover> {
override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<FileCover> {
when (val innerResult = inner.create(file, metadata)) {
is CoverResult.Hit -> return CoverResult.Hit(innerResult.cover)
is CoverResult.Miss -> {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
return CoverResult.Miss()
}
val mediaStoreUri = MediaStore.getMediaUri(context, file.uri) ?: return CoverResult.Miss()
val proj = arrayOf(MediaStore.MediaColumns._ID)
val cursor = context.contentResolver.query(mediaStoreUri, proj, null, null, null)
val uri = cursor.use {
if (it == null || !it.moveToFirst()) {
return CoverResult.Miss()
}
val id = it.getLong(it.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.buildUpon().run {
appendPath(id.toString())
appendPath("albumart")
build()
}
}
return CoverResult.Hit(CompatCover(context, uri))
}
}
}
override suspend fun cleanup(excluding: Collection<Cover>) {}
}
class CompatCover(private val context: Context, private val uri: Uri) : FileCover {
override val id = "compat:$uri"
override suspend fun fd(): ParcelFileDescriptor? {
return context.contentResolver.openFileDescriptor(uri, "r")
}
override suspend fun open() = withContext(Dispatchers.IO) {
context.contentResolver.openInputStream(uri)
}
}

View file

@ -19,13 +19,14 @@
package org.oxycblt.auxio.image.covers
import android.content.Context
import org.apache.commons.lang3.ObjectUtils.Null
import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.cover.CoverResult
import org.oxycblt.musikr.cover.MutableCovers
import org.oxycblt.musikr.fs.DeviceFile
import org.oxycblt.musikr.metadata.Metadata
class NullCovers(private val context: Context) : MutableCovers {
class NullCovers(private val context: Context) : MutableCovers<NullCover> {
override suspend fun obtain(id: String) = CoverResult.Hit(NullCover)
override suspend fun create(file: DeviceFile, metadata: Metadata) = CoverResult.Hit(NullCover)

View file

@ -23,19 +23,23 @@ 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.Cover
import org.oxycblt.musikr.cover.CoverIdentifier
import org.oxycblt.musikr.cover.CoverParams
import org.oxycblt.musikr.cover.CoverResult
import org.oxycblt.musikr.cover.FileCover
import org.oxycblt.musikr.cover.MutableCovers
interface SettingCovers {
suspend fun create(context: Context, revision: UUID): MutableCovers
suspend fun obtain(context: Context, id: String): CoverResult<FileCover>
suspend fun mutate(context: Context, revision: UUID): MutableCovers<out Cover>
}
class SettingCoversImpl
@Inject
constructor(private val imageSettings: ImageSettings, private val identifier: CoverIdentifier) :
SettingCovers {
override suspend fun create(context: Context, revision: UUID): MutableCovers =
override suspend fun mutate(context: Context, revision: UUID): MutableCovers<out Cover> =
when (imageSettings.coverMode) {
CoverMode.OFF -> NullCovers(context)
CoverMode.SAVE_SPACE -> siloedCovers(context, revision, CoverParams.of(500, 70))
@ -43,6 +47,13 @@ constructor(private val imageSettings: ImageSettings, private val identifier: Co
CoverMode.HIGH_QUALITY -> siloedCovers(context, revision, CoverParams.of(1000, 100))
}
override suspend fun obtain(context: Context, id: String): CoverResult<FileCover> {
val coverId = SiloedCoverId.parse(id) ?: return CoverResult.Miss()
val siloedCovers = SiloedCovers.from(context, coverId.silo)
val covers = CompatCovers(context, siloedCovers)
return covers.obtain(coverId.id)
}
private suspend fun siloedCovers(context: Context, revision: UUID, with: CoverParams) =
MutableSiloedCovers.from(context, CoverSilo(revision, with), identifier)
MutableCompatCovers(context, MutableSiloedCovers.from(context, CoverSilo(revision, with), identifier))
}

View file

@ -35,8 +35,8 @@ import org.oxycblt.musikr.fs.DeviceFile
import org.oxycblt.musikr.fs.app.AppFiles
import org.oxycblt.musikr.metadata.Metadata
open class SiloedCovers(private val silo: CoverSilo, private val fileCovers: FileCovers) : Covers {
override suspend fun obtain(id: String): CoverResult<SiloedCover> {
open class SiloedCovers(private val silo: CoverSilo, private val fileCovers: FileCovers) : Covers<FileCover> {
override suspend fun obtain(id: String): CoverResult<FileCover> {
val coverId = SiloedCoverId.parse(id) ?: return CoverResult.Miss()
if (coverId.silo != silo) return CoverResult.Miss()
return when (val result = fileCovers.obtain(coverId.id)) {
@ -58,8 +58,8 @@ private constructor(
private val rootDir: File,
private val silo: CoverSilo,
private val fileCovers: MutableFileCovers
) : SiloedCovers(silo, fileCovers), MutableCovers {
override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<out Cover> =
) : SiloedCovers(silo, fileCovers), MutableCovers<FileCover> {
override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<FileCover> =
when (val result = fileCovers.create(file, metadata)) {
is CoverResult.Hit -> CoverResult.Hit(SiloedCover(silo, result.cover))
is CoverResult.Miss -> CoverResult.Miss()

View file

@ -389,7 +389,7 @@ constructor(
val currentRevision = musicSettings.revision
val newRevision = currentRevision?.takeIf { withCache } ?: UUID.randomUUID()
val cache = if (withCache) storedCache.visible() else storedCache.invisible()
val covers = settingCovers.create(context, newRevision)
val covers = settingCovers.mutate(context, newRevision)
val storage = Storage(cache, covers, storedPlaylists)
val interpretation = Interpretation(nameFactory, separators, ignoreHidden)

View file

@ -22,12 +22,12 @@ import java.io.InputStream
import org.oxycblt.musikr.fs.DeviceFile
import org.oxycblt.musikr.metadata.Metadata
interface Covers {
suspend fun obtain(id: String): CoverResult<out Cover>
interface Covers<T : Cover> {
suspend fun obtain(id: String): CoverResult<T>
}
interface MutableCovers : Covers {
suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<out Cover>
interface MutableCovers<T : Cover> : Covers<T> {
suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<T>
suspend fun cleanup(excluding: Collection<Cover>)
}