musikr: make cover creation more flexible
Enables some compat cover changes I need to make.
This commit is contained in:
parent
403f93b6df
commit
25901a0f76
10 changed files with 51 additions and 37 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() }
|
||||||
|
|
|
@ -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 })
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>) {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Reference in a new issue