From f206f08e7991a1281ad9966dfa494e1a6cb6f6e7 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sat, 23 Jul 2022 09:13:38 -0600 Subject: [PATCH] music: handle multiple vorbis tags [#197] Modify ExoPlayerBackend to handle the presence of multiple vorbis tags for a particular key. This is allowed by the spec and heavily leveraged by programs like Picard. It also opens the door for better artist functionality, but that is incredibly complicated and something I don't want to implement right now. --- CHANGELOG.md | 1 + README.md | 4 +- .../java/org/oxycblt/auxio/music/Music.kt | 9 +++-- .../java/org/oxycblt/auxio/music/MusicUtil.kt | 3 ++ .../auxio/music/system/ExoPlayerBackend.kt | 37 +++++++++++-------- 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6273ed786..738d9a66e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ at the cost of longer loading times - Added support for sort tags [#172, dependent on this feature] - Added support for date tags, including more fine-grained dates [#159, dependent on this feature] - Added support for release types signifying EPs, Singles, Compilations, and more [#158, dependent on this feature] + - Added basic awareness of multi-value vorbis tags [#197, dependent on this feature] - Added Last Added sorting - Search now takes sort tags and file names in account [#184] diff --git a/README.md b/README.md index 9ad42ddf1..3b2e13ab4 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

A simple, rational music player for android.

- Latest version + Latest Version Releases @@ -11,7 +11,7 @@ - Minimum SDK + Minimum SDK Version

Changelog | FAQ | Licenses | Contributing | Architecture

diff --git a/app/src/main/java/org/oxycblt/auxio/music/Music.kt b/app/src/main/java/org/oxycblt/auxio/music/Music.kt index a0c174ae1..c4d512211 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Music.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Music.kt @@ -493,8 +493,9 @@ sealed class ReleaseType { } companion object { - fun parse(type: String): ReleaseType { - val types = type.split('+') + fun parse(type: String) = parse(type.split('+')) + + fun parse(types: List): ReleaseType { val primary = types[0].trim() // Primary types should be the first one in sequence. The spec makes no mention of @@ -523,8 +524,8 @@ sealed class ReleaseType { secondary.equals("compilation", true) -> Compilation secondary.equals("soundtrack", true) -> Soundtrack secondary.equals("mixtape/street", true) -> Mixtape - secondary.equals("live", true) -> target(Refinement.REMIX) - secondary.equals("remix", true) -> target(Refinement.LIVE) + secondary.equals("live", true) -> target(Refinement.LIVE) + secondary.equals("remix", true) -> target(Refinement.REMIX) else -> target(null) } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt index e90c3b5f3..ce4cee322 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicUtil.kt @@ -103,6 +103,9 @@ fun String.parseSortName() = /** Shortcut to parse an [ReleaseType] from a string */ fun String.parseReleaseType() = ReleaseType.parse(this) +/** Shortcut to parse a [ReleaseType] from a list of strings */ +fun List.parseReleaseType() = ReleaseType.parse(this) + /** * Decodes the genre name from an ID3(v2) constant. See [GENRE_TABLE] for the genre constant map * that Auxio uses. diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/ExoPlayerBackend.kt b/app/src/main/java/org/oxycblt/auxio/music/system/ExoPlayerBackend.kt index 6cfee8e7e..78fd42247 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/ExoPlayerBackend.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/ExoPlayerBackend.kt @@ -170,7 +170,7 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) { private fun completeAudio(metadata: Metadata) { val id3v2Tags = mutableMapOf() - val vorbisTags = mutableMapOf() + val vorbisTags = mutableMapOf>() // ExoPlayer only exposes ID3v2 and Vorbis metadata, which constitutes the vast majority // of audio formats. Load both of these types of tags into separate maps, letting the @@ -189,7 +189,11 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) { val id = tag.key.sanitize().uppercase() val value = tag.value.sanitize() if (value.isNotEmpty()) { - vorbisTags[id] = value + if (vorbisTags.containsKey(id)) { + vorbisTags[id]!!.add(value) + } else { + vorbisTags[id] = mutableListOf(value) + } } } } @@ -274,16 +278,16 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) { } } - private fun populateVorbis(tags: Map) { + private fun populateVorbis(tags: Map>) { // (Sort) Title - tags["TITLE"]?.let { audio.title = it } - tags["TITLESORT"]?.let { audio.sortTitle = it } + tags["TITLE"]?.let { audio.title = it[0] } + tags["TITLESORT"]?.let { audio.sortTitle = it[0] } // Track - tags["TRACKNUMBER"]?.parsePositionNum()?.let { audio.track = it } + tags["TRACKNUMBER"]?.run { get(0).parsePositionNum() }?.let { audio.track = it } // Disc - tags["DISCNUMBER"]?.parsePositionNum()?.let { audio.disc = it } + tags["DISCNUMBER"]?.run { get(0).parsePositionNum() }?.let { audio.disc = it } // Vorbis dates are less complicated, but there are still several types // Our hierarchy for dates is as such: @@ -291,24 +295,25 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) { // 2. Date, as it is the most common date type // 3. Year, as old vorbis tags tended to use this (I know this because it's the only // tag that android supports, so it must be 15 years old or more!) - (tags["ORIGINALDATE"]?.parseTimestamp() - ?: tags["DATE"]?.parseTimestamp() ?: tags["YEAR"]?.parseYear()) + (tags["ORIGINALDATE"]?.run { get(0).parseTimestamp() } + ?: tags["DATE"]?.run { get(0).parseTimestamp() } + ?: tags["YEAR"]?.run { get(0).parseYear() }) ?.let { audio.date = it } // (Sort) Album - tags["ALBUM"]?.let { audio.album = it } - tags["ALBUMSORT"]?.let { audio.sortAlbum = it } + tags["ALBUM"]?.let { audio.album = it.joinToString() } + tags["ALBUMSORT"]?.let { audio.sortAlbum = it.joinToString() } // (Sort) Artist - tags["ARTIST"]?.let { audio.artist = it } - tags["ARTISTSORT"]?.let { audio.sortArtist = it } + tags["ARTIST"]?.let { audio.artist = it.joinToString() } + tags["ARTISTSORT"]?.let { audio.sortArtist = it.joinToString() } // (Sort) Album artist - tags["ALBUMARTIST"]?.let { audio.albumArtist = it } - tags["ALBUMARTISTSORT"]?.let { audio.sortAlbumArtist = it } + tags["ALBUMARTIST"]?.let { audio.albumArtist = it.joinToString() } + tags["ALBUMARTISTSORT"]?.let { audio.sortAlbumArtist = it.joinToString() } // Genre, no ID3 rules here - tags["GENRE"]?.let { audio.genre = it } + tags["GENRE"]?.let { audio.genre = it.joinToString() } // Release type tags["RELEASETYPE"]?.parseReleaseType()?.let { audio.releaseType = it }