musikr: introduce folder covers
Like cover.png, cover.jpg, etc.
This commit is contained in:
parent
8104985a4e
commit
a7000bc9e5
9 changed files with 215 additions and 153 deletions
|
@ -1,94 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 Auxio Project
|
|
||||||
* CompatCovers.kt is part of Auxio.
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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.device.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) }
|
|
||||||
}
|
|
|
@ -28,14 +28,16 @@ import org.oxycblt.musikr.cover.CoverIdentifier
|
||||||
import org.oxycblt.musikr.cover.CoverParams
|
import org.oxycblt.musikr.cover.CoverParams
|
||||||
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.FolderCovers
|
||||||
import org.oxycblt.musikr.cover.MutableCovers
|
import org.oxycblt.musikr.cover.MutableCovers
|
||||||
|
import org.oxycblt.musikr.cover.MutableFolderCovers
|
||||||
|
|
||||||
interface SettingCovers {
|
interface SettingCovers {
|
||||||
suspend fun mutate(context: Context, revision: UUID): MutableCovers<out Cover>
|
suspend fun mutate(context: Context, revision: UUID): MutableCovers<out Cover>
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun immutable(context: Context): Covers<out FileCover> =
|
fun immutable(context: Context): Covers<FileCover> =
|
||||||
CompatCovers(context, BaseSiloedCovers(context))
|
Covers.chain(BaseSiloedCovers(context), FolderCovers(context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +54,7 @@ constructor(private val imageSettings: ImageSettings, private val identifier: Co
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun siloedCovers(context: Context, revision: UUID, with: CoverParams) =
|
private suspend fun siloedCovers(context: Context, revision: UUID, with: CoverParams) =
|
||||||
MutableCompatCovers(
|
MutableCovers.chain(
|
||||||
context, MutableSiloedCovers.from(context, CoverSilo(revision, with), identifier))
|
MutableSiloedCovers.from(context, CoverSilo(revision, with), identifier),
|
||||||
|
MutableFolderCovers(context))
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,8 @@ 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.fs.device.DeviceFile
|
|
||||||
import org.oxycblt.musikr.fs.app.AppFiles
|
import org.oxycblt.musikr.fs.app.AppFiles
|
||||||
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
import org.oxycblt.musikr.metadata.Metadata
|
import org.oxycblt.musikr.metadata.Metadata
|
||||||
|
|
||||||
class BaseSiloedCovers(private val context: Context) : Covers<FileCover> {
|
class BaseSiloedCovers(private val context: Context) : Covers<FileCover> {
|
||||||
|
|
|
@ -24,12 +24,58 @@ import org.oxycblt.musikr.metadata.Metadata
|
||||||
|
|
||||||
interface Covers<T : Cover> {
|
interface Covers<T : Cover> {
|
||||||
suspend fun obtain(id: String): CoverResult<T>
|
suspend fun obtain(id: String): CoverResult<T>
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun <R : Cover, T : R> chain(vararg many: Covers<out T>): Covers<R> =
|
||||||
|
object : Covers<R> {
|
||||||
|
override suspend fun obtain(id: String): CoverResult<R> {
|
||||||
|
for (cover in many) {
|
||||||
|
val result = cover.obtain(id)
|
||||||
|
if (result is CoverResult.Hit) {
|
||||||
|
return CoverResult.Hit(result.cover)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CoverResult.Miss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MutableCovers<T : Cover> : Covers<T> {
|
interface MutableCovers<T : Cover> : Covers<T> {
|
||||||
suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<T>
|
suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<T>
|
||||||
|
|
||||||
suspend fun cleanup(excluding: Collection<Cover>)
|
suspend fun cleanup(excluding: Collection<Cover>)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun <R : Cover, T : R> chain(vararg many: MutableCovers<out T>): MutableCovers<R> =
|
||||||
|
object : MutableCovers<R> {
|
||||||
|
override suspend fun obtain(id: String): CoverResult<R> {
|
||||||
|
for (cover in many) {
|
||||||
|
val result = cover.obtain(id)
|
||||||
|
if (result is CoverResult.Hit) {
|
||||||
|
return CoverResult.Hit(result.cover)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CoverResult.Miss()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<R> {
|
||||||
|
for (cover in many) {
|
||||||
|
val result = cover.create(file, metadata)
|
||||||
|
if (result is CoverResult.Hit) {
|
||||||
|
return CoverResult.Hit(result.cover)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CoverResult.Miss()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun cleanup(excluding: Collection<Cover>) {
|
||||||
|
for (cover in many) {
|
||||||
|
cover.cleanup(excluding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface CoverResult<T : Cover> {
|
sealed interface CoverResult<T : Cover> {
|
||||||
|
@ -42,6 +88,10 @@ interface Cover {
|
||||||
val id: String
|
val id: String
|
||||||
|
|
||||||
suspend fun open(): InputStream?
|
suspend fun open(): InputStream?
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean
|
||||||
|
|
||||||
|
override fun hashCode(): Int
|
||||||
}
|
}
|
||||||
|
|
||||||
class CoverCollection private constructor(val covers: List<Cover>) {
|
class CoverCollection private constructor(val covers: List<Cover>) {
|
||||||
|
|
|
@ -19,9 +19,9 @@
|
||||||
package org.oxycblt.musikr.cover
|
package org.oxycblt.musikr.cover
|
||||||
|
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import org.oxycblt.musikr.fs.device.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.fs.device.DeviceFile
|
||||||
import org.oxycblt.musikr.metadata.Metadata
|
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) :
|
||||||
|
|
114
musikr/src/main/java/org/oxycblt/musikr/cover/FolderCovers.kt
Normal file
114
musikr/src/main/java/org/oxycblt/musikr/cover/FolderCovers.kt
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Auxio Project
|
||||||
|
* FolderCovers.kt is part of Auxio.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.oxycblt.musikr.cover
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.ParcelFileDescriptor
|
||||||
|
import java.io.InputStream
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.oxycblt.musikr.fs.device.DeviceDirectory
|
||||||
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
|
import org.oxycblt.musikr.metadata.Metadata
|
||||||
|
|
||||||
|
open class FolderCovers(private val context: Context) : Covers<FolderCover> {
|
||||||
|
override suspend fun obtain(id: String): CoverResult<FolderCover> {
|
||||||
|
// Parse the ID to get the directory URI
|
||||||
|
if (!id.startsWith("folder:")) {
|
||||||
|
return CoverResult.Miss()
|
||||||
|
}
|
||||||
|
|
||||||
|
val directoryUri = id.substring("folder:".length)
|
||||||
|
val uri = Uri.parse(directoryUri)
|
||||||
|
return CoverResult.Hit(FolderCoverImpl(context, uri))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MutableFolderCovers(private val context: Context) :
|
||||||
|
FolderCovers(context), MutableCovers<FolderCover> {
|
||||||
|
override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<FolderCover> {
|
||||||
|
val parent = file.parent
|
||||||
|
val coverFile = findCoverInDirectory(parent) ?: return CoverResult.Miss()
|
||||||
|
return CoverResult.Hit(FolderCoverImpl(context, coverFile.uri))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun cleanup(excluding: Collection<Cover>) {
|
||||||
|
// No cleanup needed for folder covers as they are external files
|
||||||
|
// that should not be managed by the app
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun findCoverInDirectory(directory: DeviceDirectory): DeviceFile? {
|
||||||
|
return directory.children
|
||||||
|
.mapNotNull { node -> if (node is DeviceFile && isCoverArtFile(node)) node else null }
|
||||||
|
.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isCoverArtFile(file: DeviceFile): Boolean {
|
||||||
|
val filename = requireNotNull(file.path.name).lowercase()
|
||||||
|
val mimeType = file.mimeType.lowercase()
|
||||||
|
|
||||||
|
// Check if the file is an image
|
||||||
|
if (!mimeType.startsWith("image/")) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common cover art filenames
|
||||||
|
val coverNames =
|
||||||
|
listOf(
|
||||||
|
"cover",
|
||||||
|
"folder",
|
||||||
|
"album",
|
||||||
|
"albumart",
|
||||||
|
"front",
|
||||||
|
"artwork",
|
||||||
|
"art",
|
||||||
|
"folder",
|
||||||
|
"cover")
|
||||||
|
|
||||||
|
// Check if the filename matches any common cover art names
|
||||||
|
// Also check for case variations (e.g., Cover.jpg, COVER.JPG)
|
||||||
|
val filenameWithoutExt = filename.substringBeforeLast(".")
|
||||||
|
val extension = filename.substringAfterLast(".", "")
|
||||||
|
|
||||||
|
return coverNames.any { coverName ->
|
||||||
|
filenameWithoutExt.equals(coverName, ignoreCase = true) &&
|
||||||
|
(extension.equals("jpg", ignoreCase = true) ||
|
||||||
|
extension.equals("jpeg", ignoreCase = true) ||
|
||||||
|
extension.equals("png", ignoreCase = true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FolderCover : FileCover
|
||||||
|
|
||||||
|
private data class FolderCoverImpl(
|
||||||
|
private val context: Context,
|
||||||
|
private val uri: Uri,
|
||||||
|
) : FolderCover {
|
||||||
|
override val id = "folder:$uri"
|
||||||
|
|
||||||
|
override suspend fun fd(): ParcelFileDescriptor? =
|
||||||
|
withContext(Dispatchers.IO) { context.contentResolver.openFileDescriptor(uri, "r") }
|
||||||
|
|
||||||
|
override suspend fun open(): InputStream? =
|
||||||
|
withContext(Dispatchers.IO) { context.contentResolver.openInputStream(uri) }
|
||||||
|
}
|
|
@ -25,8 +25,8 @@ import android.provider.DocumentsContract
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.flow
|
|
||||||
import kotlinx.coroutines.flow.flatMapMerge
|
import kotlinx.coroutines.flow.flatMapMerge
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import org.oxycblt.musikr.fs.MusicLocation
|
import org.oxycblt.musikr.fs.MusicLocation
|
||||||
import org.oxycblt.musikr.fs.Path
|
import org.oxycblt.musikr.fs.Path
|
||||||
|
|
||||||
|
@ -43,22 +43,19 @@ private class DeviceFilesImpl(private val contentResolver: ContentResolver) : De
|
||||||
override fun explore(locations: Flow<MusicLocation>, ignoreHidden: Boolean): Flow<DeviceNode> =
|
override fun explore(locations: Flow<MusicLocation>, ignoreHidden: Boolean): Flow<DeviceNode> =
|
||||||
locations.flatMapMerge { location ->
|
locations.flatMapMerge { location ->
|
||||||
// Create a root directory for each location
|
// Create a root directory for each location
|
||||||
val rootDirectory = DeviceDirectory(
|
val rootDirectory =
|
||||||
uri = location.uri,
|
DeviceDirectory(
|
||||||
path = location.path,
|
uri = location.uri, path = location.path, parent = null, children = emptyFlow())
|
||||||
parent = null,
|
|
||||||
children = emptyFlow()
|
|
||||||
)
|
|
||||||
|
|
||||||
// Set up the children flow for the root directory
|
// Set up the children flow for the root directory
|
||||||
rootDirectory.children = exploreDirectoryImpl(
|
rootDirectory.children =
|
||||||
|
exploreDirectoryImpl(
|
||||||
contentResolver,
|
contentResolver,
|
||||||
location.uri,
|
location.uri,
|
||||||
DocumentsContract.getTreeDocumentId(location.uri),
|
DocumentsContract.getTreeDocumentId(location.uri),
|
||||||
location.path,
|
location.path,
|
||||||
rootDirectory,
|
rootDirectory,
|
||||||
ignoreHidden
|
ignoreHidden)
|
||||||
)
|
|
||||||
|
|
||||||
// Return a flow that emits the root directory
|
// Return a flow that emits the root directory
|
||||||
flow { emit(rootDirectory) }
|
flow { emit(rootDirectory) }
|
||||||
|
@ -101,22 +98,17 @@ private class DeviceFilesImpl(private val contentResolver: ContentResolver) : De
|
||||||
|
|
||||||
if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) {
|
if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) {
|
||||||
// Create a directory node with empty children flow initially
|
// Create a directory node with empty children flow initially
|
||||||
val directory = DeviceDirectory(
|
val directory =
|
||||||
|
DeviceDirectory(
|
||||||
uri = childUri,
|
uri = childUri,
|
||||||
path = newPath,
|
path = newPath,
|
||||||
parent = parent,
|
parent = parent,
|
||||||
children = emptyFlow()
|
children = emptyFlow())
|
||||||
)
|
|
||||||
|
|
||||||
// Set up the children flow for this directory
|
// Set up the children flow for this directory
|
||||||
directory.children = exploreDirectoryImpl(
|
directory.children =
|
||||||
contentResolver,
|
exploreDirectoryImpl(
|
||||||
rootUri,
|
contentResolver, rootUri, childId, newPath, directory, ignoreHidden)
|
||||||
childId,
|
|
||||||
newPath,
|
|
||||||
directory,
|
|
||||||
ignoreHidden
|
|
||||||
)
|
|
||||||
|
|
||||||
// Emit the directory node
|
// Emit the directory node
|
||||||
emit(directory)
|
emit(directory)
|
||||||
|
@ -129,9 +121,7 @@ private class DeviceFilesImpl(private val contentResolver: ContentResolver) : De
|
||||||
path = newPath,
|
path = newPath,
|
||||||
size = size,
|
size = size,
|
||||||
modifiedMs = lastModified,
|
modifiedMs = lastModified,
|
||||||
parent = parent
|
parent = parent))
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,11 +31,11 @@ import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.merge
|
import kotlinx.coroutines.flow.merge
|
||||||
import org.oxycblt.musikr.Storage
|
import org.oxycblt.musikr.Storage
|
||||||
|
import org.oxycblt.musikr.fs.MusicLocation
|
||||||
import org.oxycblt.musikr.fs.device.DeviceDirectory
|
import org.oxycblt.musikr.fs.device.DeviceDirectory
|
||||||
import org.oxycblt.musikr.fs.device.DeviceFile
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
import org.oxycblt.musikr.fs.device.DeviceNode
|
|
||||||
import org.oxycblt.musikr.fs.MusicLocation
|
|
||||||
import org.oxycblt.musikr.fs.device.DeviceFiles
|
import org.oxycblt.musikr.fs.device.DeviceFiles
|
||||||
|
import org.oxycblt.musikr.fs.device.DeviceNode
|
||||||
import org.oxycblt.musikr.playlist.PlaylistFile
|
import org.oxycblt.musikr.playlist.PlaylistFile
|
||||||
import org.oxycblt.musikr.playlist.db.StoredPlaylists
|
import org.oxycblt.musikr.playlist.db.StoredPlaylists
|
||||||
import org.oxycblt.musikr.playlist.m3u.M3U
|
import org.oxycblt.musikr.playlist.m3u.M3U
|
||||||
|
@ -57,9 +57,7 @@ private class ExploreStepImpl(
|
||||||
val audios =
|
val audios =
|
||||||
deviceFiles
|
deviceFiles
|
||||||
.explore(locations.asFlow())
|
.explore(locations.asFlow())
|
||||||
.flattenFilter {
|
.flattenFilter { it.mimeType.startsWith("audio/") || it.mimeType == M3U.MIME_TYPE }
|
||||||
it.mimeType.startsWith("audio/") || it.mimeType == M3U.MIME_TYPE
|
|
||||||
}
|
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
.buffer()
|
.buffer()
|
||||||
val playlists =
|
val playlists =
|
||||||
|
@ -71,7 +69,8 @@ private class ExploreStepImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private fun Flow<DeviceNode>.flattenFilter(block: (DeviceFile) -> Boolean): Flow<ExploreNode> = flow {
|
private fun Flow<DeviceNode>.flattenFilter(block: (DeviceFile) -> Boolean): Flow<ExploreNode> =
|
||||||
|
flow {
|
||||||
collect {
|
collect {
|
||||||
val recurse = mutableListOf<Flow<ExploreNode>>()
|
val recurse = mutableListOf<Flow<ExploreNode>>()
|
||||||
when {
|
when {
|
||||||
|
|
|
@ -20,8 +20,8 @@ package org.oxycblt.musikr.tag.interpret
|
||||||
|
|
||||||
import org.oxycblt.musikr.Interpretation
|
import org.oxycblt.musikr.Interpretation
|
||||||
import org.oxycblt.musikr.Music
|
import org.oxycblt.musikr.Music
|
||||||
import org.oxycblt.musikr.fs.device.DeviceFile
|
|
||||||
import org.oxycblt.musikr.fs.Format
|
import org.oxycblt.musikr.fs.Format
|
||||||
|
import org.oxycblt.musikr.fs.device.DeviceFile
|
||||||
import org.oxycblt.musikr.pipeline.RawSong
|
import org.oxycblt.musikr.pipeline.RawSong
|
||||||
import org.oxycblt.musikr.tag.Disc
|
import org.oxycblt.musikr.tag.Disc
|
||||||
import org.oxycblt.musikr.tag.Name
|
import org.oxycblt.musikr.tag.Name
|
||||||
|
|
Loading…
Reference in a new issue