music: add additional album types

Add compilation and soundtrack album type support.

I find use in these, so implement them.
This commit is contained in:
OxygenCobalt 2022-07-19 10:32:03 -06:00
parent 24062aa623
commit 36bb729e67
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
8 changed files with 65 additions and 32 deletions

View file

@ -51,6 +51,8 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
* - The RecyclerView data for each fragment * - The RecyclerView data for each fragment
* - The sorts for each type of data * - The sorts for each type of data
* @author OxygenCobalt * @author OxygenCobalt
*
* TODO: Unify how detail items are indicated
*/ */
class DetailViewModel(application: Application) : class DetailViewModel(application: Application) :
AndroidViewModel(application), MusicStore.Callback { AndroidViewModel(application), MusicStore.Callback {
@ -242,16 +244,11 @@ class DetailViewModel(application: Application) :
logD("Refreshing artist data") logD("Refreshing artist data")
val data = mutableListOf<Item>(artist) val data = mutableListOf<Item>(artist)
val albums = Sort(Sort.Mode.ByYear, false).albums(artist.albums) val albums = Sort(Sort.Mode.ByYear, false).albums(artist.albums)
val byType = albums.groupBy { it.type ?: Album.Type.Album }
byType.keys.sorted().forEachIndexed { index, type ->
val typeString =
when (type) {
Album.Type.Album -> R.string.lbl_albums
Album.Type.EP -> R.string.lbl_eps
Album.Type.Single -> R.string.lbl_singles
}
data.add(Header(-2L - index, typeString)) // Organize albums by their release type. We do not dor
val byType = albums.groupBy { it.type ?: Album.Type.ALBUM }
byType.keys.sorted().forEachIndexed { index, type ->
data.add(Header(-2L - index, type.pluralStringRes))
data.addAll(unlikelyToBeNull(byType[type])) data.addAll(unlikelyToBeNull(byType[type]))
} }
@ -303,6 +300,7 @@ class DetailViewModel(application: Application) :
val genre = currentGenre.value val genre = currentGenre.value
if (genre != null) { if (genre != null) {
logD("Genre changed, refreshing data")
val newGenre = library.sanitize(genre).also { _currentGenre.value = it } val newGenre = library.sanitize(genre).also { _currentGenre.value = it }
if (newGenre != null) { if (newGenre != null) {
refreshGenreData(newGenre) refreshGenreData(newGenre)

View file

@ -135,7 +135,7 @@ private class AlbumDetailViewHolder private constructor(private val binding: Ite
if (item.type != null) { if (item.type != null) {
context.getString( context.getString(
R.string.fmt_four, R.string.fmt_four,
context.getString(item.type.string), context.getString(item.type.stringRes),
date, date,
songCount, songCount,
duration) duration)

View file

@ -256,16 +256,56 @@ data class Album(
} }
enum class Type { enum class Type {
Album, ALBUM,
EP, EP,
Single; SINGLE,
COMPILATION,
SOUNDTRACK;
val string: Int // I only implemented the release types that I use. If there is sufficient demand,
// I'll extend them to these release types.
// REMIX, LIVE, MIXTAPE
val stringRes: Int
get() = get() =
when (this) { when (this) {
Album -> R.string.lbl_album ALBUM -> R.string.lbl_album
EP -> R.string.lbl_ep EP -> R.string.lbl_ep
Single -> R.string.lbl_single SINGLE -> R.string.lbl_single
COMPILATION -> R.string.lbl_compilation
SOUNDTRACK -> R.string.lbl_soundtrack
}
val pluralStringRes: Int
get() =
when (this) {
ALBUM -> R.string.lbl_albums
EP -> R.string.lbl_eps
SINGLE -> R.string.lbl_singles
COMPILATION -> R.string.lbl_compilations
SOUNDTRACK -> R.string.lbl_soundtracks
}
companion object {
fun parse(type: String): Type {
// Release types (at least to MusicBrainz) are formatted as <primary> + <secondary>
// where primary is something like "album", "ep", or "single", and secondary is
// "compilation", "soundtrack", etc. Use the secondary type as the album type before
// falling back to the primary type.
val primarySecondary = type.split('+').map { it.trim() }
return primarySecondary.getOrNull(1)?.parseReleaseType()
?: primarySecondary[0].parseReleaseType() ?: ALBUM
}
private fun String.parseReleaseType() =
when {
equals("album", ignoreCase = true) -> ALBUM
equals("ep", ignoreCase = true) -> EP
equals("single", ignoreCase = true) -> SINGLE
equals("compilation", ignoreCase = true) -> COMPILATION
equals("soundtrack", ignoreCase = true) -> SOUNDTRACK
else -> null
}
} }
} }
} }

View file

@ -100,17 +100,8 @@ fun String.parseSortName() =
else -> this else -> this
} }
/** Parse a release type from this string into an [Album.Type]. Handles MusicBrainz separators. */ /** Shortcut to parse an [Album.Type] from a string */
fun String.parseReleaseType() = fun String.parseAlbumType() = Album.Type.parse(this)
parseReleaseTypeImpl() ?: split("+", limit = 2)[0].trim().parseReleaseTypeImpl()
private fun String.parseReleaseTypeImpl() =
when (this) {
"album" -> Album.Type.Album
"ep" -> Album.Type.EP
"single" -> Album.Type.Single
else -> null
}
/** /**
* Decodes the genre name from an ID3(v2) constant. See [GENRE_TABLE] for the genre constant map * Decodes the genre name from an ID3(v2) constant. See [GENRE_TABLE] for the genre constant map

View file

@ -28,9 +28,9 @@ import com.google.android.exoplayer2.metadata.vorbis.VorbisComment
import org.oxycblt.auxio.music.Date import org.oxycblt.auxio.music.Date
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.audioUri import org.oxycblt.auxio.music.audioUri
import org.oxycblt.auxio.music.parseAlbumType
import org.oxycblt.auxio.music.parseId3GenreName import org.oxycblt.auxio.music.parseId3GenreName
import org.oxycblt.auxio.music.parsePositionNum import org.oxycblt.auxio.music.parsePositionNum
import org.oxycblt.auxio.music.parseReleaseType
import org.oxycblt.auxio.music.parseTimestamp import org.oxycblt.auxio.music.parseTimestamp
import org.oxycblt.auxio.music.parseYear import org.oxycblt.auxio.music.parseYear
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -247,8 +247,8 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) {
// Genre, with the weird ID3 rules. // Genre, with the weird ID3 rules.
tags["TCON"]?.let { audio.genre = it.parseId3GenreName() } tags["TCON"]?.let { audio.genre = it.parseId3GenreName() }
// Release type // Release type (GRP1 is sometimes used for this, so fall back to it)
(tags["TXXX:MusicBrainz Album Type"] ?: tags["GRP1"])?.parseReleaseType()?.let { (tags["TXXX:MusicBrainz Album Type"] ?: tags["GRP1"])?.parseAlbumType()?.let {
audio.albumType = it audio.albumType = it
} }
} }
@ -311,7 +311,7 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) {
tags["GENRE"]?.let { audio.genre = it } tags["GENRE"]?.let { audio.genre = it }
// Release type // Release type
tags["RELEASETYPE"]?.parseReleaseType()?.let { audio.albumType = it } tags["RELEASETYPE"]?.parseAlbumType()?.let { audio.albumType = it }
} }
/** /**

View file

@ -302,7 +302,7 @@ class Indexer {
// If album types aren't used by the music library (Represented by all songs having // If album types aren't used by the music library (Represented by all songs having
// an album type), there is no point in displaying them. // an album type), there is no point in displaying them.
val enableAlbumTypes = songs.any { it._albumType != Album.Type.Album } val enableAlbumTypes = songs.any { it._albumType != Album.Type.ALBUM }
if (!enableAlbumTypes) { if (!enableAlbumTypes) {
logD("No distinct album types detected, ignoring them") logD("No distinct album types detected, ignoring them")
} }

View file

@ -371,7 +371,7 @@ abstract class MediaStoreBackend : Indexer.Backend {
_date = date, _date = date,
_albumName = requireNotNull(album) { "Malformed audio: No album name" }, _albumName = requireNotNull(album) { "Malformed audio: No album name" },
_albumSortName = sortAlbum, _albumSortName = sortAlbum,
_albumType = albumType ?: Album.Type.Album, _albumType = albumType ?: Album.Type.ALBUM,
_albumCoverUri = _albumCoverUri =
requireNotNull(albumId) { "Malformed audio: No album id" }.albumCoverUri, requireNotNull(albumId) { "Malformed audio: No album id" }.albumCoverUri,
_artistName = artist, _artistName = artist,

View file

@ -23,6 +23,10 @@
<string name="lbl_eps">EPs</string> <string name="lbl_eps">EPs</string>
<string name="lbl_single">Single</string> <string name="lbl_single">Single</string>
<string name="lbl_singles">Singles</string> <string name="lbl_singles">Singles</string>
<string name="lbl_compilation">Compilation</string>
<string name="lbl_compilations">Compilations</string>
<string name="lbl_soundtrack">Soundtrack</string>
<string name="lbl_soundtracks">Soundtracks</string>
<string name="lbl_artist">Artist</string> <string name="lbl_artist">Artist</string>
<string name="lbl_artists">Artists</string> <string name="lbl_artists">Artists</string>