music: modify model configuration

Do a couple things to the music models:
1. Make the genre field non-nullable. This is because I beleive I've
largely eliminated the genre bugs in previous versions and future ones
can be caught with a crash screen I plan to add.
2. Make the initial album grouping process use hashCode instead of a
pair of names. This just helps with loading speed in general, albeit I
am slightly worried that it may result in improper grouping if some
edge case appears.
This commit is contained in:
OxygenCobalt 2022-02-08 06:53:53 -07:00
parent f4217a337a
commit 04bec3161f
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
2 changed files with 42 additions and 34 deletions

View file

@ -65,17 +65,17 @@ data class Song(
/** The track number of this song. */
val track: Int,
/** Internal field. Do not use. */
val _mediaStoreId: Long,
val internalMediaStoreId: Long,
/** Internal field. Do not use. */
val _mediaStoreArtistName: String?,
val internalMediaStoreArtistName: String?,
/** Internal field. Do not use. */
val _mediaStoreAlbumArtistName: String?,
val internalMediaStoreAlbumArtistName: String?,
/** Internal field. Do not use. */
val _mediaStoreAlbumId: Long,
val internalMediaStoreAlbumId: Long,
/** Internal field. Do not use. */
val _mediaStoreAlbumName: String,
val internalMediaStoreAlbumName: String,
/** Internal field. Do not use. */
val _mediaStoreYear: Int
val internalMediaStoreYear: Int
) : Music() {
override val id: Long get() {
var result = name.hashCode().toLong()
@ -88,7 +88,7 @@ data class Song(
/** The URI for this song. */
val uri: Uri get() = ContentUris.withAppendedId(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, _mediaStoreId
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, internalMediaStoreId
)
/** The duration of this song, in seconds (rounded down) */
val seconds: Long get() = duration / 1000
@ -100,8 +100,8 @@ data class Song(
val album: Album get() = requireNotNull(mAlbum)
var mGenre: Genre? = null
/** The genre of this song. May be null due to MediaStore insanity. */
val genre: Genre? get() = mGenre
/** The genre of this song. Will be an "unknown genre" if the song does not have any. */
val genre: Genre get() = requireNotNull(mGenre)
/** An album name resolved to this song in particular. */
val resolvedAlbumName: String get() =
@ -109,15 +109,29 @@ data class Song(
/** An artist name resolved to this song in particular. */
val resolvedArtistName: String get() =
_mediaStoreArtistName ?: album.artist.resolvedName
internalMediaStoreArtistName ?: album.artist.resolvedName
/** Internal field. Do not use. */
val internalGroupingId: Int get() {
var result = internalGroupingArtistName.lowercase().hashCode()
result = 31 * result + internalMediaStoreAlbumName.lowercase().hashCode()
return result
}
/** Internal field. Do not use. */
val internalGroupingArtistName: String get() = internalMediaStoreAlbumArtistName
?: internalMediaStoreArtistName ?: MediaStore.UNKNOWN_STRING
/** Internal field. Do not use. **/
val internalMissingGenre: Boolean get() = mGenre == null
/** Internal method. Do not use. */
fun mediaStoreLinkAlbum(album: Album) {
fun internalLinkAlbum(album: Album) {
mAlbum = album
}
/** Internal method. Do not use. */
fun mediaStoreLinkGenre(genre: Genre) {
fun internalLinkGenre(genre: Genre) {
mGenre = genre
}
}
@ -134,11 +148,11 @@ data class Album(
/** The songs of this album. */
val songs: List<Song>,
/** Internal field. Do not use. */
val _mediaStoreArtistName: String,
val internalGroupingArtistName: String,
) : MusicParent() {
init {
for (song in songs) {
song.mediaStoreLinkAlbum(this)
song.internalLinkAlbum(this)
}
}
@ -165,7 +179,7 @@ data class Album(
artist.resolvedName
/** Internal method. Do not use. */
fun mediaStoreLinkArtist(artist: Artist) {
fun internalLinkArtist(artist: Artist) {
mArtist = artist
}
}
@ -182,7 +196,7 @@ data class Artist(
) : MusicParent() {
init {
for (album in albums) {
album.mediaStoreLinkArtist(this)
album.internalLinkArtist(this)
}
}
@ -202,7 +216,7 @@ data class Genre(
) : MusicParent() {
init {
for (song in songs) {
song.mediaStoreLinkGenre(this)
song.internalLinkGenre(this)
}
}

View file

@ -92,6 +92,7 @@ class MusicLoader {
for (song in songs) {
try {
song.album.artist
song.genre
} catch (e: Exception) {
logE("Found malformed song: ${song.name}")
throw e
@ -187,18 +188,13 @@ class MusicLoader {
}
songs = songs.distinctBy {
it.name to it._mediaStoreAlbumName to it._mediaStoreArtistName to it._mediaStoreAlbumArtistName to it.track to it.duration
it.name to it.internalMediaStoreAlbumName to it.internalMediaStoreArtistName to it.internalMediaStoreAlbumArtistName to it.track to it.duration
}.toMutableList()
return songs
}
private fun buildAlbums(songs: List<Song>): List<Album> {
// When assigning an artist to an album, use the album artist first, then the
// normal artist, and then the internal representation of an unknown artist name.
fun Song.resolveAlbumArtistName() = _mediaStoreAlbumArtistName ?: _mediaStoreArtistName
?: MediaStore.UNKNOWN_STRING
// Group up songs by their lowercase artist and album name. This serves two purposes:
// 1. Sometimes artist names can be styled differently, e.g "Rammstein" vs. "RAMMSTEIN".
// This makes sure both of those are resolved into a single artist called "Rammstein"
@ -209,9 +205,7 @@ class MusicLoader {
// the template, but it seems to work pretty well.
val albums = mutableListOf<Album>()
val songsByAlbum = songs.groupBy { song ->
val albumName = song._mediaStoreAlbumName
val artistName = song.resolveAlbumArtistName()
Pair(albumName.lowercase(), artistName.lowercase())
song.internalGroupingId
}
for (entry in songsByAlbum) {
@ -220,14 +214,14 @@ class MusicLoader {
// Use the song with the latest year as our metadata song.
// This allows us to replicate the LAST_YEAR field, which is useful as it means that
// weird years like "0" wont show up if there are alternatives.
val templateSong = requireNotNull(albumSongs.maxByOrNull { it._mediaStoreYear })
val albumName = templateSong._mediaStoreAlbumName
val albumYear = templateSong._mediaStoreYear
val templateSong = requireNotNull(albumSongs.maxByOrNull { it.internalMediaStoreYear })
val albumName = templateSong.internalMediaStoreAlbumName
val albumYear = templateSong.internalMediaStoreYear
val albumCoverUri = ContentUris.withAppendedId(
Uri.parse("content://media/external/audio/albumart"),
templateSong._mediaStoreAlbumId
templateSong.internalMediaStoreAlbumId
)
val artistName = templateSong.resolveAlbumArtistName()
val artistName = templateSong.internalGroupingArtistName
albums.add(
Album(
@ -245,7 +239,7 @@ class MusicLoader {
private fun buildArtists(context: Context, albums: List<Album>): List<Artist> {
val artists = mutableListOf<Artist>()
val albumsByArtist = albums.groupBy { it._mediaStoreArtistName }
val albumsByArtist = albums.groupBy { it.internalGroupingArtistName }
for (entry in albumsByArtist) {
val artistName = entry.key
@ -318,7 +312,7 @@ class MusicLoader {
}
}
val songsWithoutGenres = songs.filter { it.genre == null }
val songsWithoutGenres = songs.filter { it.internalMissingGenre }
if (songsWithoutGenres.isNotEmpty()) {
// Songs that don't have a genre will be thrown into an unknown genre.
@ -350,7 +344,7 @@ class MusicLoader {
while (cursor.moveToNext()) {
val id = cursor.getLong(idIndex)
songs.find { it._mediaStoreId == id }?.let { song ->
songs.find { it.internalMediaStoreId == id }?.let { song ->
genreSongs.add(song)
}
}