musikr: make cover creation more flexible

Enables some compat cover changes I need to make.
This commit is contained in:
Alexander Capehart 2025-02-26 14:51:23 -07:00
parent 403f93b6df
commit 25901a0f76
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
10 changed files with 51 additions and 37 deletions

View file

@ -29,7 +29,7 @@ import kotlinx.coroutines.runBlocking
import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.image.covers.SiloedCoverId import org.oxycblt.auxio.image.covers.SiloedCoverId
import org.oxycblt.auxio.image.covers.SiloedCovers import org.oxycblt.auxio.image.covers.SiloedCovers
import org.oxycblt.musikr.cover.ObtainResult import org.oxycblt.musikr.cover.CoverResult
class CoverProvider : ContentProvider() { class CoverProvider : ContentProvider() {
override fun onCreate(): Boolean = true override fun onCreate(): Boolean = true
@ -43,8 +43,8 @@ class CoverProvider : ContentProvider() {
return runBlocking { return runBlocking {
val siloedCovers = SiloedCovers.from(requireNotNull(context), coverId.silo) val siloedCovers = SiloedCovers.from(requireNotNull(context), coverId.silo)
when (val res = siloedCovers.obtain(id)) { when (val res = siloedCovers.obtain(id)) {
is ObtainResult.Hit -> res.cover.fd() is CoverResult.Hit -> res.cover.fd()
is ObtainResult.Miss -> null is CoverResult.Miss -> null
} }
} }
} }

View file

@ -409,7 +409,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
@Px val iconSize: Int? @Px val iconSize: Int?
) : Drawable() { ) : Drawable() {
init { 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. // StyledImageView.
DrawableCompat.setTintList(inner, context.getColorCompat(R.color.sel_on_cover_bg)) DrawableCompat.setTintList(inner, context.getColorCompat(R.color.sel_on_cover_bg))
} }

View file

@ -20,13 +20,15 @@ package org.oxycblt.auxio.image.covers
import android.content.Context import android.content.Context
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.cover.CoverResult
import org.oxycblt.musikr.cover.MutableCovers 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 { 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<Cover>) { override suspend fun cleanup(excluding: Collection<Cover>) {
context.coversDir().listFiles()?.forEach { it.deleteRecursively() } context.coversDir().listFiles()?.forEach { it.deleteRecursively() }

View file

@ -25,21 +25,23 @@ import kotlinx.coroutines.withContext
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.cover.CoverFormat import org.oxycblt.musikr.cover.CoverFormat
import org.oxycblt.musikr.cover.CoverIdentifier import org.oxycblt.musikr.cover.CoverIdentifier
import org.oxycblt.musikr.cover.CoverResult
import org.oxycblt.musikr.cover.Covers import org.oxycblt.musikr.cover.Covers
import org.oxycblt.musikr.cover.FileCover import org.oxycblt.musikr.cover.FileCover
import org.oxycblt.musikr.cover.FileCovers import org.oxycblt.musikr.cover.FileCovers
import org.oxycblt.musikr.cover.MutableCovers import org.oxycblt.musikr.cover.MutableCovers
import org.oxycblt.musikr.cover.MutableFileCovers 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.fs.app.AppFiles
import org.oxycblt.musikr.metadata.Metadata
open class SiloedCovers(private val silo: CoverSilo, private val fileCovers: FileCovers) : Covers { open class SiloedCovers(private val silo: CoverSilo, private val fileCovers: FileCovers) : Covers {
override suspend fun obtain(id: String): ObtainResult<SiloedCover> { override suspend fun obtain(id: String): CoverResult<SiloedCover> {
val coverId = SiloedCoverId.parse(id) ?: return ObtainResult.Miss() val coverId = SiloedCoverId.parse(id) ?: return CoverResult.Miss()
if (coverId.silo != silo) return ObtainResult.Miss() if (coverId.silo != silo) return CoverResult.Miss()
return when (val result = fileCovers.obtain(coverId.id)) { return when (val result = fileCovers.obtain(coverId.id)) {
is ObtainResult.Hit -> ObtainResult.Hit(SiloedCover(silo, result.cover)) is CoverResult.Hit -> CoverResult.Hit(SiloedCover(silo, result.cover))
is ObtainResult.Miss -> ObtainResult.Miss() is CoverResult.Miss -> CoverResult.Miss()
} }
} }
@ -57,7 +59,11 @@ private constructor(
private val silo: CoverSilo, private val silo: CoverSilo,
private val fileCovers: MutableFileCovers private val fileCovers: MutableFileCovers
) : SiloedCovers(silo, fileCovers), MutableCovers { ) : SiloedCovers(silo, fileCovers), MutableCovers {
override suspend fun write(data: ByteArray) = SiloedCover(silo, fileCovers.write(data)) override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<out Cover> =
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<Cover>) { override suspend fun cleanup(excluding: Collection<Cover>) {
fileCovers.cleanup(excluding.filterIsInstance<SiloedCover>().map { it.innerCover }) fileCovers.cleanup(excluding.filterIsInstance<SiloedCover>().map { it.innerCover })

View file

@ -31,8 +31,8 @@ import androidx.room.RoomDatabase
import androidx.room.Transaction import androidx.room.Transaction
import androidx.room.TypeConverter import androidx.room.TypeConverter
import androidx.room.TypeConverters import androidx.room.TypeConverters
import org.oxycblt.musikr.cover.CoverResult
import org.oxycblt.musikr.cover.Covers import org.oxycblt.musikr.cover.Covers
import org.oxycblt.musikr.cover.ObtainResult
import org.oxycblt.musikr.fs.DeviceFile import org.oxycblt.musikr.fs.DeviceFile
import org.oxycblt.musikr.metadata.Properties import org.oxycblt.musikr.metadata.Properties
import org.oxycblt.musikr.pipeline.RawSong import org.oxycblt.musikr.pipeline.RawSong
@ -122,9 +122,9 @@ internal data class CachedSong(
val cover = val cover =
when (val result = coverId?.let { covers.obtain(it) }) { when (val result = coverId?.let { covers.obtain(it) }) {
// We found the cover. // 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. // 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. // No cover in the first place, can ignore.
null -> null null -> null
} }

View file

@ -19,21 +19,23 @@
package org.oxycblt.musikr.cover package org.oxycblt.musikr.cover
import java.io.InputStream import java.io.InputStream
import org.oxycblt.musikr.fs.DeviceFile
import org.oxycblt.musikr.metadata.Metadata
interface Covers { interface Covers {
suspend fun obtain(id: String): ObtainResult<out Cover> suspend fun obtain(id: String): CoverResult<out Cover>
} }
interface MutableCovers : Covers { interface MutableCovers : Covers {
suspend fun write(data: ByteArray): Cover suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<out Cover>
suspend fun cleanup(excluding: Collection<Cover>) suspend fun cleanup(excluding: Collection<Cover>)
} }
sealed interface ObtainResult<T : Cover> { sealed interface CoverResult<T : Cover> {
data class Hit<T : Cover>(val cover: T) : ObtainResult<T> data class Hit<T : Cover>(val cover: T) : CoverResult<T>
class Miss<T : Cover> : ObtainResult<T> class Miss<T : Cover> : CoverResult<T>
} }
interface Cover { interface Cover {

View file

@ -19,17 +19,19 @@
package org.oxycblt.musikr.cover package org.oxycblt.musikr.cover
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import org.oxycblt.musikr.fs.DeviceFile
import org.oxycblt.musikr.fs.app.AppFile import org.oxycblt.musikr.fs.app.AppFile
import org.oxycblt.musikr.fs.app.AppFiles import org.oxycblt.musikr.fs.app.AppFiles
import org.oxycblt.musikr.metadata.Metadata
open class FileCovers(private val appFiles: AppFiles, private val coverFormat: CoverFormat) : open class FileCovers(private val appFiles: AppFiles, private val coverFormat: CoverFormat) :
Covers { Covers {
override suspend fun obtain(id: String): ObtainResult<FileCover> { override suspend fun obtain(id: String): CoverResult<FileCover> {
val file = appFiles.find(getFileName(id)) val file = appFiles.find(getFileName(id))
return if (file != null) { return if (file != null) {
ObtainResult.Hit(FileCoverImpl(id, file)) CoverResult.Hit(FileCoverImpl(id, file))
} else { } else {
ObtainResult.Miss() CoverResult.Miss()
} }
} }
@ -41,10 +43,11 @@ class MutableFileCovers(
private val coverFormat: CoverFormat, private val coverFormat: CoverFormat,
private val coverIdentifier: CoverIdentifier private val coverIdentifier: CoverIdentifier
) : FileCovers(appFiles, coverFormat), MutableCovers { ) : FileCovers(appFiles, coverFormat), MutableCovers {
override suspend fun write(data: ByteArray): FileCover { override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<FileCover> {
val data = metadata.cover ?: return CoverResult.Miss()
val id = coverIdentifier.identify(data) val id = coverIdentifier.identify(data)
val file = appFiles.write(getFileName(id)) { coverFormat.transcodeInto(data, it) } val coverFile = appFiles.write(getFileName(id)) { coverFormat.transcodeInto(data, it) }
return FileCoverImpl(id, file) return CoverResult.Hit(FileCoverImpl(id, coverFile))
} }
override suspend fun cleanup(excluding: Collection<Cover>) { override suspend fun cleanup(excluding: Collection<Cover>) {

View file

@ -20,7 +20,7 @@ package org.oxycblt.musikr.fs
import android.net.Uri import android.net.Uri
internal data class DeviceFile( data class DeviceFile(
val uri: Uri, val uri: Uri,
val mimeType: String, val mimeType: String,
val path: Path, val path: Path,

View file

@ -18,7 +18,7 @@
package org.oxycblt.musikr.metadata package org.oxycblt.musikr.metadata
internal data class Metadata( data class Metadata(
val id3v2: Map<String, List<String>>, val id3v2: Map<String, List<String>>,
val xiph: Map<String, List<String>>, val xiph: Map<String, List<String>>,
val mp4: Map<String, List<String>>, val mp4: Map<String, List<String>>,
@ -53,7 +53,7 @@ internal data class Metadata(
} }
} }
internal data class Properties( data class Properties(
val mimeType: String, val mimeType: String,
val durationMs: Long, val durationMs: Long,
val bitrateKbps: Int, val bitrateKbps: Int,

View file

@ -35,6 +35,7 @@ import org.oxycblt.musikr.Storage
import org.oxycblt.musikr.cache.Cache import org.oxycblt.musikr.cache.Cache
import org.oxycblt.musikr.cache.CacheResult import org.oxycblt.musikr.cache.CacheResult
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.cover.CoverResult
import org.oxycblt.musikr.cover.MutableCovers import org.oxycblt.musikr.cover.MutableCovers
import org.oxycblt.musikr.fs.DeviceFile import org.oxycblt.musikr.fs.DeviceFile
import org.oxycblt.musikr.metadata.MetadataExtractor import org.oxycblt.musikr.metadata.MetadataExtractor
@ -62,7 +63,7 @@ private class ExtractStepImpl(
private val metadataExtractor: MetadataExtractor, private val metadataExtractor: MetadataExtractor,
private val tagParser: TagParser, private val tagParser: TagParser,
private val cacheFactory: Cache.Factory, private val cacheFactory: Cache.Factory,
private val storedCovers: MutableCovers private val covers: MutableCovers
) : ExtractStep { ) : ExtractStep {
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
override fun extract(nodes: Flow<ExploreNode>): Flow<ExtractedMusic> { override fun extract(nodes: Flow<ExploreNode>): Flow<ExtractedMusic> {
@ -84,7 +85,7 @@ private class ExtractStepImpl(
readDistributedFlow.flows readDistributedFlow.flows
.map { flow -> .map { flow ->
flow flow
.map { wrap(it) { file -> cache.read(file, storedCovers) } } .map { wrap(it) { file -> cache.read(file, covers) } }
.flowOn(Dispatchers.IO) .flowOn(Dispatchers.IO)
.buffer(Channel.UNLIMITED) .buffer(Channel.UNLIMITED)
} }
@ -123,8 +124,10 @@ private class ExtractStepImpl(
if (extractedMetadata != null) { if (extractedMetadata != null) {
val tags = tagParser.parse(extractedMetadata) val tags = tagParser.parse(extractedMetadata)
val cover = val cover =
extractedMetadata.cover?.let { when (val result =
storedCovers.write(it) covers.create(f, extractedMetadata)) {
is CoverResult.Hit -> result.cover
else -> null
} }
val rawSong = val rawSong =
RawSong( RawSong(
@ -175,8 +178,6 @@ private class ExtractStepImpl(
return merged.onCompletion { cache.finalize() } return merged.onCompletion { cache.finalize() }
} }
private data class FileWith<T>(val file: DeviceFile, val with: T)
} }
internal data class RawSong( internal data class RawSong(