music: re-add mediastore genre parsing

Re-add MediaStore genre parsing to the init() step.

This time, to play along with the new abstraction, the query is done in
initialization and loaded into a map that is then used when populating
raw audio.
This commit is contained in:
Alexander Capehart 2022-09-09 19:54:37 -06:00
parent 06d6495dcd
commit 189f712eaa
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
2 changed files with 58 additions and 2 deletions

View file

@ -33,6 +33,7 @@ import org.oxycblt.auxio.music.directoryCompat
import org.oxycblt.auxio.music.mediaStoreVolumeNameCompat import org.oxycblt.auxio.music.mediaStoreVolumeNameCompat
import org.oxycblt.auxio.music.queryCursor import org.oxycblt.auxio.music.queryCursor
import org.oxycblt.auxio.music.storageVolumesCompat import org.oxycblt.auxio.music.storageVolumesCompat
import org.oxycblt.auxio.music.useQuery
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.contentResolverSafe import org.oxycblt.auxio.util.contentResolverSafe
import org.oxycblt.auxio.util.getSystemServiceCompat import org.oxycblt.auxio.util.getSystemServiceCompat
@ -120,6 +121,7 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
private var albumArtistIndex = -1 private var albumArtistIndex = -1
private val settings = Settings(context) private val settings = Settings(context)
private val genreNamesMap = mutableMapOf<Long, List<String>>()
private val _volumes = mutableListOf<StorageVolume>() private val _volumes = mutableListOf<StorageVolume>()
protected val volumes: List<StorageVolume> protected val volumes: List<StorageVolume>
@ -127,6 +129,9 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
/** Initialize this instance by making a query over the media database. */ /** Initialize this instance by making a query over the media database. */
open fun init(): Cursor { open fun init(): Cursor {
logD("Initializing")
val start = System.currentTimeMillis()
cacheLayer.init() cacheLayer.init()
val storageManager = context.getSystemServiceCompat(StorageManager::class) val storageManager = context.getSystemServiceCompat(StorageManager::class)
@ -163,7 +168,7 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
selector += ')' selector += ')'
} }
logD("Starting query [proj: ${projection.toList()}, selector: $selector, args: $args]") logD("Starting song query [proj: ${projection.toList()}, selector: $selector, args: $args]")
val cursor = val cursor =
requireNotNull( requireNotNull(
@ -191,6 +196,46 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
artistIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ARTIST) artistIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ARTIST)
albumArtistIndex = cursor.getColumnIndexOrThrow(AUDIO_COLUMN_ALBUM_ARTIST) albumArtistIndex = cursor.getColumnIndexOrThrow(AUDIO_COLUMN_ALBUM_ARTIST)
logD("Song query succeeded [Projected total: ${cursor.count}]")
logD("Assembling genre map")
// Since we can't obtain the genre tag from a song query, we must construct
// our own equivalent from genre database queries. Theoretically, this isn't
// needed since MetadataLayer will fill this in for us, but there are some
// obscure formats where genre support is only really covered by this.
context.contentResolverSafe.useQuery(
MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI,
arrayOf(MediaStore.Audio.Genres._ID, MediaStore.Audio.Genres.NAME)
) { genreCursor ->
val idIndex = genreCursor.getColumnIndexOrThrow(MediaStore.Audio.Genres._ID)
val nameIndex = genreCursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.NAME)
while (genreCursor.moveToNext()) {
// We can't assume what format these genres are derived from, so we just have
// to assume ID3v2 rules.
val id = genreCursor.getLong(idIndex)
val names = (genreCursor.getStringOrNull(nameIndex) ?: continue)
.parseId3GenreNames(settings)
context.contentResolverSafe.useQuery(
MediaStore.Audio.Genres.Members.getContentUri(VOLUME_EXTERNAL, id),
arrayOf(MediaStore.Audio.Genres.Members._ID)
) { cursor ->
val songIdIndex =
cursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.Members._ID)
while (cursor.moveToNext()) {
// Assume that a song can't inhabit multiple genre entries, as I doubt
// Android is smart enough to separate genres into separators.
genreNamesMap[cursor.getLong(songIdIndex)] = names
}
}
}
}
logD("Finished initialization in ${System.currentTimeMillis() - start}ms")
return cursor return cursor
} }
@ -221,7 +266,7 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
raw.dateModified = cursor.getLong(dateAddedIndex) raw.dateModified = cursor.getLong(dateAddedIndex)
if (cacheLayer.maybePopulateCachedRaw(raw)) { if (cacheLayer.maybePopulateCachedRaw(raw)) {
// We found a valid cache entry, no need to build it. // We found a valid cache entry, no need to extract metadata.
logD("Found cached raw: ${raw.name}") logD("Found cached raw: ${raw.name}")
return true return true
} }
@ -301,6 +346,8 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
// The album artist field is nullable and never has placeholder values. // The album artist field is nullable and never has placeholder values.
raw.albumArtistNames = raw.albumArtistNames =
cursor.getStringOrNull(albumArtistIndex)?.maybeParseSeparators(settings) cursor.getStringOrNull(albumArtistIndex)?.maybeParseSeparators(settings)
raw.genreNames = genreNamesMap[raw.mediaStoreId]
} }
companion object { companion object {
@ -313,6 +360,13 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
@Suppress("InlinedApi") @Suppress("InlinedApi")
private const val AUDIO_COLUMN_ALBUM_ARTIST = MediaStore.Audio.AudioColumns.ALBUM_ARTIST private const val AUDIO_COLUMN_ALBUM_ARTIST = MediaStore.Audio.AudioColumns.ALBUM_ARTIST
/**
* External has existed since at least API 21, but no constant existed for it until API 29.
* This constant is safe to use.
*/
@Suppress("InlinedApi")
private const val VOLUME_EXTERNAL = MediaStore.VOLUME_EXTERNAL
/** /**
* The base selector that works across all versions of android. Does not exclude * The base selector that works across all versions of android. Does not exclude
* directories. * directories.

View file

@ -62,6 +62,8 @@ import org.oxycblt.auxio.util.logW
* directly work with music loading, making such redundant. * directly work with music loading, making such redundant.
* *
* @author OxygenCobalt * @author OxygenCobalt
*
* TODO: Try to replace TaskGuard with yield when possible
*/ */
class Indexer { class Indexer {
private var lastResponse: Response? = null private var lastResponse: Response? = null