From 25901a0f764e6168b05acae466c36a0be1a444fa Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Wed, 26 Feb 2025 14:51:23 -0700 Subject: [PATCH] musikr: make cover creation more flexible Enables some compat cover changes I need to make. --- .../org/oxycblt/auxio/image/CoverProvider.kt | 6 +++--- .../java/org/oxycblt/auxio/image/CoverView.kt | 2 +- .../oxycblt/auxio/image/covers/NullCovers.kt | 8 +++++--- .../auxio/image/covers/SiloedCovers.kt | 20 ++++++++++++------- .../org/oxycblt/musikr/cache/CacheDatabase.kt | 6 +++--- .../java/org/oxycblt/musikr/cover/Covers.kt | 12 ++++++----- .../org/oxycblt/musikr/cover/FileCovers.kt | 15 ++++++++------ .../java/org/oxycblt/musikr/fs/DeviceFile.kt | 2 +- .../org/oxycblt/musikr/metadata/Metadata.kt | 4 ++-- .../oxycblt/musikr/pipeline/ExtractStep.kt | 13 ++++++------ 10 files changed, 51 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/image/CoverProvider.kt b/app/src/main/java/org/oxycblt/auxio/image/CoverProvider.kt index 976fc64e4..1c2a45820 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/CoverProvider.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/CoverProvider.kt @@ -29,7 +29,7 @@ import kotlinx.coroutines.runBlocking import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.image.covers.SiloedCoverId import org.oxycblt.auxio.image.covers.SiloedCovers -import org.oxycblt.musikr.cover.ObtainResult +import org.oxycblt.musikr.cover.CoverResult class CoverProvider : ContentProvider() { override fun onCreate(): Boolean = true @@ -43,8 +43,8 @@ class CoverProvider : ContentProvider() { return runBlocking { val siloedCovers = SiloedCovers.from(requireNotNull(context), coverId.silo) when (val res = siloedCovers.obtain(id)) { - is ObtainResult.Hit -> res.cover.fd() - is ObtainResult.Miss -> null + is CoverResult.Hit -> res.cover.fd() + is CoverResult.Miss -> null } } } diff --git a/app/src/main/java/org/oxycblt/auxio/image/CoverView.kt b/app/src/main/java/org/oxycblt/auxio/image/CoverView.kt index eb402545c..03f731618 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/CoverView.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/CoverView.kt @@ -409,7 +409,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr @Px val iconSize: Int? ) : Drawable() { init { - // Re-tint the drawable to use the analogous "on surfaceg" color for + // Re-tint the drawable to use the analogous "on surface" color for // StyledImageView. DrawableCompat.setTintList(inner, context.getColorCompat(R.color.sel_on_cover_bg)) } 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 7aeb4da5d..3a9fc4478 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 @@ -20,13 +20,15 @@ package org.oxycblt.auxio.image.covers import android.content.Context import org.oxycblt.musikr.cover.Cover +import org.oxycblt.musikr.cover.CoverResult import org.oxycblt.musikr.cover.MutableCovers -import org.oxycblt.musikr.cover.ObtainResult +import org.oxycblt.musikr.fs.DeviceFile +import org.oxycblt.musikr.metadata.Metadata class NullCovers(private val context: Context) : MutableCovers { - override suspend fun obtain(id: String) = ObtainResult.Hit(NullCover) + override suspend fun obtain(id: String) = CoverResult.Hit(NullCover) - override suspend fun write(data: ByteArray): Cover = NullCover + override suspend fun create(file: DeviceFile, metadata: Metadata) = CoverResult.Hit(NullCover) override suspend fun cleanup(excluding: Collection) { context.coversDir().listFiles()?.forEach { it.deleteRecursively() } 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 253d0dacf..d116b662c 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 @@ -25,21 +25,23 @@ import kotlinx.coroutines.withContext import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.cover.CoverFormat import org.oxycblt.musikr.cover.CoverIdentifier +import org.oxycblt.musikr.cover.CoverResult import org.oxycblt.musikr.cover.Covers import org.oxycblt.musikr.cover.FileCover import org.oxycblt.musikr.cover.FileCovers import org.oxycblt.musikr.cover.MutableCovers import org.oxycblt.musikr.cover.MutableFileCovers -import org.oxycblt.musikr.cover.ObtainResult +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): ObtainResult { - val coverId = SiloedCoverId.parse(id) ?: return ObtainResult.Miss() - if (coverId.silo != silo) return ObtainResult.Miss() + override suspend fun obtain(id: String): CoverResult { + val coverId = SiloedCoverId.parse(id) ?: return CoverResult.Miss() + if (coverId.silo != silo) return CoverResult.Miss() return when (val result = fileCovers.obtain(coverId.id)) { - is ObtainResult.Hit -> ObtainResult.Hit(SiloedCover(silo, result.cover)) - is ObtainResult.Miss -> ObtainResult.Miss() + is CoverResult.Hit -> CoverResult.Hit(SiloedCover(silo, result.cover)) + is CoverResult.Miss -> CoverResult.Miss() } } @@ -57,7 +59,11 @@ private constructor( private val silo: CoverSilo, private val fileCovers: MutableFileCovers ) : SiloedCovers(silo, fileCovers), MutableCovers { - override suspend fun write(data: ByteArray) = SiloedCover(silo, fileCovers.write(data)) + override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult = + when (val result = fileCovers.create(file, metadata)) { + is CoverResult.Hit -> CoverResult.Hit(SiloedCover(silo, result.cover)) + is CoverResult.Miss -> CoverResult.Miss() + } override suspend fun cleanup(excluding: Collection) { fileCovers.cleanup(excluding.filterIsInstance().map { it.innerCover }) diff --git a/musikr/src/main/java/org/oxycblt/musikr/cache/CacheDatabase.kt b/musikr/src/main/java/org/oxycblt/musikr/cache/CacheDatabase.kt index d1700777c..2e70f2721 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/cache/CacheDatabase.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/cache/CacheDatabase.kt @@ -31,8 +31,8 @@ import androidx.room.RoomDatabase import androidx.room.Transaction import androidx.room.TypeConverter import androidx.room.TypeConverters +import org.oxycblt.musikr.cover.CoverResult import org.oxycblt.musikr.cover.Covers -import org.oxycblt.musikr.cover.ObtainResult import org.oxycblt.musikr.fs.DeviceFile import org.oxycblt.musikr.metadata.Properties import org.oxycblt.musikr.pipeline.RawSong @@ -122,9 +122,9 @@ internal data class CachedSong( val cover = when (val result = coverId?.let { covers.obtain(it) }) { // We found the cover. - is ObtainResult.Hit -> result.cover + is CoverResult.Hit -> result.cover // We actually didn't find the cover, can't safely convert. - is ObtainResult.Miss -> return null + is CoverResult.Miss -> return null // No cover in the first place, can ignore. null -> null } diff --git a/musikr/src/main/java/org/oxycblt/musikr/cover/Covers.kt b/musikr/src/main/java/org/oxycblt/musikr/cover/Covers.kt index e3f41b386..9972dda06 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/cover/Covers.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/cover/Covers.kt @@ -19,21 +19,23 @@ package org.oxycblt.musikr.cover import java.io.InputStream +import org.oxycblt.musikr.fs.DeviceFile +import org.oxycblt.musikr.metadata.Metadata interface Covers { - suspend fun obtain(id: String): ObtainResult + suspend fun obtain(id: String): CoverResult } interface MutableCovers : Covers { - suspend fun write(data: ByteArray): Cover + suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult suspend fun cleanup(excluding: Collection) } -sealed interface ObtainResult { - data class Hit(val cover: T) : ObtainResult +sealed interface CoverResult { + data class Hit(val cover: T) : CoverResult - class Miss : ObtainResult + class Miss : CoverResult } interface Cover { diff --git a/musikr/src/main/java/org/oxycblt/musikr/cover/FileCovers.kt b/musikr/src/main/java/org/oxycblt/musikr/cover/FileCovers.kt index 4c8390869..6a38f9946 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/cover/FileCovers.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/cover/FileCovers.kt @@ -19,17 +19,19 @@ package org.oxycblt.musikr.cover import android.os.ParcelFileDescriptor +import org.oxycblt.musikr.fs.DeviceFile import org.oxycblt.musikr.fs.app.AppFile import org.oxycblt.musikr.fs.app.AppFiles +import org.oxycblt.musikr.metadata.Metadata open class FileCovers(private val appFiles: AppFiles, private val coverFormat: CoverFormat) : Covers { - override suspend fun obtain(id: String): ObtainResult { + override suspend fun obtain(id: String): CoverResult { val file = appFiles.find(getFileName(id)) return if (file != null) { - ObtainResult.Hit(FileCoverImpl(id, file)) + CoverResult.Hit(FileCoverImpl(id, file)) } else { - ObtainResult.Miss() + CoverResult.Miss() } } @@ -41,10 +43,11 @@ class MutableFileCovers( private val coverFormat: CoverFormat, private val coverIdentifier: CoverIdentifier ) : FileCovers(appFiles, coverFormat), MutableCovers { - override suspend fun write(data: ByteArray): FileCover { + override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult { + val data = metadata.cover ?: return CoverResult.Miss() val id = coverIdentifier.identify(data) - val file = appFiles.write(getFileName(id)) { coverFormat.transcodeInto(data, it) } - return FileCoverImpl(id, file) + val coverFile = appFiles.write(getFileName(id)) { coverFormat.transcodeInto(data, it) } + return CoverResult.Hit(FileCoverImpl(id, coverFile)) } override suspend fun cleanup(excluding: Collection) { diff --git a/musikr/src/main/java/org/oxycblt/musikr/fs/DeviceFile.kt b/musikr/src/main/java/org/oxycblt/musikr/fs/DeviceFile.kt index 6baac772f..25b0dfbbe 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/fs/DeviceFile.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/fs/DeviceFile.kt @@ -20,7 +20,7 @@ package org.oxycblt.musikr.fs import android.net.Uri -internal data class DeviceFile( +data class DeviceFile( val uri: Uri, val mimeType: String, val path: Path, diff --git a/musikr/src/main/java/org/oxycblt/musikr/metadata/Metadata.kt b/musikr/src/main/java/org/oxycblt/musikr/metadata/Metadata.kt index 758e4483a..ba16830b5 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/metadata/Metadata.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/metadata/Metadata.kt @@ -18,7 +18,7 @@ package org.oxycblt.musikr.metadata -internal data class Metadata( +data class Metadata( val id3v2: Map>, val xiph: Map>, val mp4: Map>, @@ -53,7 +53,7 @@ internal data class Metadata( } } -internal data class Properties( +data class Properties( val mimeType: String, val durationMs: Long, val bitrateKbps: Int, diff --git a/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt b/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt index 7ffff175f..f14eb9f0e 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt @@ -35,6 +35,7 @@ import org.oxycblt.musikr.Storage import org.oxycblt.musikr.cache.Cache import org.oxycblt.musikr.cache.CacheResult 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.MetadataExtractor @@ -62,7 +63,7 @@ private class ExtractStepImpl( private val metadataExtractor: MetadataExtractor, private val tagParser: TagParser, private val cacheFactory: Cache.Factory, - private val storedCovers: MutableCovers + private val covers: MutableCovers ) : ExtractStep { @OptIn(ExperimentalCoroutinesApi::class) override fun extract(nodes: Flow): Flow { @@ -84,7 +85,7 @@ private class ExtractStepImpl( readDistributedFlow.flows .map { flow -> flow - .map { wrap(it) { file -> cache.read(file, storedCovers) } } + .map { wrap(it) { file -> cache.read(file, covers) } } .flowOn(Dispatchers.IO) .buffer(Channel.UNLIMITED) } @@ -123,8 +124,10 @@ private class ExtractStepImpl( if (extractedMetadata != null) { val tags = tagParser.parse(extractedMetadata) val cover = - extractedMetadata.cover?.let { - storedCovers.write(it) + when (val result = + covers.create(f, extractedMetadata)) { + is CoverResult.Hit -> result.cover + else -> null } val rawSong = RawSong( @@ -175,8 +178,6 @@ private class ExtractStepImpl( return merged.onCompletion { cache.finalize() } } - - private data class FileWith(val file: DeviceFile, val with: T) } internal data class RawSong(