music: move cover parsing to indexing
This drastically slows music loading, but my hope is that in practice most of the slowdown is actually in ExoPlayer's metadata extractor and if I switch off of that things will actually improve. Maybe.
This commit is contained in:
parent
b53b7a0c6a
commit
42390f4b3f
8 changed files with 49 additions and 32 deletions
|
@ -18,6 +18,8 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.music
|
package org.oxycblt.auxio.music
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -209,6 +211,7 @@ class MusicRepositoryImpl
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val musikr: Musikr,
|
private val musikr: Musikr,
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
private val tagDatabase: TagDatabase,
|
private val tagDatabase: TagDatabase,
|
||||||
private val musicSettings: MusicSettings
|
private val musicSettings: MusicSettings
|
||||||
) : MusicRepository {
|
) : MusicRepository {
|
||||||
|
@ -358,9 +361,10 @@ constructor(
|
||||||
|
|
||||||
val storage =
|
val storage =
|
||||||
if (withCache) {
|
if (withCache) {
|
||||||
Storage(TagCache.full(tagDatabase), StoredCovers.buildOn())
|
Storage(TagCache.full(tagDatabase), StoredCovers.from(context, "covers"))
|
||||||
} else {
|
} else {
|
||||||
Storage(TagCache.writeOnly(tagDatabase), StoredCovers.new())
|
// TODO: Revisioned covers
|
||||||
|
Storage(TagCache.writeOnly(tagDatabase), StoredCovers.from(context, "covers"))
|
||||||
}
|
}
|
||||||
val newLibrary =
|
val newLibrary =
|
||||||
musikr.run(
|
musikr.run(
|
||||||
|
|
|
@ -23,6 +23,6 @@ import org.oxycblt.musikr.tag.Name
|
||||||
import org.oxycblt.musikr.tag.cache.TagCache
|
import org.oxycblt.musikr.tag.cache.TagCache
|
||||||
import org.oxycblt.musikr.tag.interpret.Separators
|
import org.oxycblt.musikr.tag.interpret.Separators
|
||||||
|
|
||||||
data class Storage(val tagCache: TagCache, val coverEditor: StoredCovers.Editor)
|
data class Storage(val tagCache: TagCache, val storedCovers: StoredCovers)
|
||||||
|
|
||||||
data class Interpretation(val nameFactory: Name.Known.Factory, val separators: Separators)
|
data class Interpretation(val nameFactory: Name.Known.Factory, val separators: Separators)
|
||||||
|
|
|
@ -19,26 +19,28 @@
|
||||||
package org.oxycblt.musikr.cover
|
package org.oxycblt.musikr.cover
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
interface CoverFiles {
|
internal interface CoverFiles {
|
||||||
suspend fun read(id: String): InputStream?
|
suspend fun read(id: String): InputStream?
|
||||||
|
|
||||||
suspend fun write(id: String, data: ByteArray)
|
suspend fun write(id: String, data: ByteArray)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun from(context: Context, path: String, format: CoverFormat): CoverFiles =
|
||||||
|
CoverFilesImpl(context, File(context.filesDir, path), format)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CoverFilesImpl
|
private class CoverFilesImpl(
|
||||||
@Inject
|
private val context: Context,
|
||||||
constructor(
|
private val dir: File,
|
||||||
@ApplicationContext private val context: Context,
|
|
||||||
private val coverFormat: CoverFormat
|
private val coverFormat: CoverFormat
|
||||||
) : CoverFiles {
|
) : CoverFiles {
|
||||||
private val fileMutexes = mutableMapOf<String, Mutex>()
|
private val fileMutexes = mutableMapOf<String, Mutex>()
|
||||||
|
@ -61,12 +63,12 @@ constructor(
|
||||||
val fileMutex = getMutexForFile(id)
|
val fileMutex = getMutexForFile(id)
|
||||||
|
|
||||||
fileMutex.withLock {
|
fileMutex.withLock {
|
||||||
val targetFile = File(context.filesDir, getTargetFilePath(id))
|
val targetFile = File(dir, getTargetFilePath(id))
|
||||||
if (targetFile.exists()) {
|
if (targetFile.exists()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val tempFile = File(context.filesDir, getTempFilePath(id))
|
val tempFile = File(dir, getTempFilePath(id))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
tempFile.outputStream().use { coverFormat.transcodeInto(data, it) }
|
tempFile.outputStream().use { coverFormat.transcodeInto(data, it) }
|
||||||
|
|
|
@ -22,15 +22,18 @@ import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
interface CoverFormat {
|
internal interface CoverFormat {
|
||||||
val extension: String
|
val extension: String
|
||||||
|
|
||||||
fun transcodeInto(data: ByteArray, output: OutputStream): Boolean
|
fun transcodeInto(data: ByteArray, output: OutputStream): Boolean
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun webp(): CoverFormat = WebpCoverFormat()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CoverFormatImpl @Inject constructor() : CoverFormat {
|
private class WebpCoverFormat() : CoverFormat {
|
||||||
override val extension = EXTENSION
|
override val extension = EXTENSION
|
||||||
|
|
||||||
override fun transcodeInto(data: ByteArray, output: OutputStream) =
|
override fun transcodeInto(data: ByteArray, output: OutputStream) =
|
||||||
|
|
|
@ -19,13 +19,16 @@
|
||||||
package org.oxycblt.musikr.cover
|
package org.oxycblt.musikr.cover
|
||||||
|
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
interface CoverIdentifier {
|
interface CoverIdentifier {
|
||||||
suspend fun identify(data: ByteArray): String
|
suspend fun identify(data: ByteArray): String
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun md5(): CoverIdentifier = MD5CoverIdentifier()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CoverIdentifierImpl @Inject constructor() : CoverIdentifier {
|
private class MD5CoverIdentifier() : CoverIdentifier {
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
override suspend fun identify(data: ByteArray): String {
|
override suspend fun identify(data: ByteArray): String {
|
||||||
val digest =
|
val digest =
|
||||||
|
|
|
@ -22,16 +22,9 @@ import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
interface CoverModule {
|
interface CoverModule {
|
||||||
@Singleton @Binds fun appFiles(impl: CoverFilesImpl): CoverFiles
|
@Binds fun coverParser(impl: CoverParserImpl): CoverParser
|
||||||
|
|
||||||
@Binds fun coverIdentifier(identifierImpl: CoverIdentifierImpl): CoverIdentifier
|
|
||||||
|
|
||||||
@Binds fun coverFormat(coverFormatImpl: CoverFormatImpl): CoverFormat
|
|
||||||
|
|
||||||
@Binds fun coverExtractor(coverExtractor: CoverParserImpl): CoverParser
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,18 +18,30 @@
|
||||||
|
|
||||||
package org.oxycblt.musikr.cover
|
package org.oxycblt.musikr.cover
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
interface StoredCovers {
|
interface StoredCovers {
|
||||||
suspend fun read(cover: Cover.Single): InputStream?
|
suspend fun read(cover: Cover.Single): InputStream?
|
||||||
|
|
||||||
interface Editor {
|
suspend fun write(data: ByteArray): Cover.Single?
|
||||||
suspend fun write(data: ByteArray): Cover.Single?
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
suspend fun buildOn(): Editor = TODO()
|
fun from(context: Context, path: String): StoredCovers =
|
||||||
|
FileStoredCovers(
|
||||||
fun new(): Editor = TODO()
|
CoverIdentifier.md5(), CoverFiles.from(context, path, CoverFormat.webp()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class FileStoredCovers(
|
||||||
|
private val coverIdentifier: CoverIdentifier,
|
||||||
|
private val coverFiles: CoverFiles
|
||||||
|
) : StoredCovers {
|
||||||
|
override suspend fun read(cover: Cover.Single) = coverFiles.read(cover.key)
|
||||||
|
|
||||||
|
override suspend fun write(data: ByteArray) =
|
||||||
|
coverIdentifier.identify(data).let { key ->
|
||||||
|
coverFiles.write(key, data)
|
||||||
|
Cover.Single(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ constructor(
|
||||||
val metadata = metadataExtractor.extract(node.file)
|
val metadata = metadataExtractor.extract(node.file)
|
||||||
val tags = tagParser.parse(node.file, metadata)
|
val tags = tagParser.parse(node.file, metadata)
|
||||||
val coverData = coverParser.extract(metadata)
|
val coverData = coverParser.extract(metadata)
|
||||||
val cover = coverData?.let { storage.coverEditor.write(it) }
|
val cover = coverData?.let { storage.storedCovers.write(it) }
|
||||||
ExtractedMusic.Song(node.file, tags, cover)
|
ExtractedMusic.Song(node.file, tags, cover)
|
||||||
}
|
}
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
|
|
Loading…
Reference in a new issue