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.
This commit is contained in:
OxygenCobalt 2022-07-23 09:13:38 -06:00
parent 0212887610
commit f206f08e79
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 32 additions and 22 deletions

View file

@ -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]

View file

@ -3,7 +3,7 @@
<h4 align="center">A simple, rational music player for android.</h4>
<p align="center">
<a href="https://github.com/oxygencobalt/Auxio/releases/tag/v2.5.0">
<img alt="Latest version" src="https://img.shields.io/static/v1?label=tag&message=v2.5.0&color=0D5AF5">
<img alt="Latest Version" src="https://img.shields.io/static/v1?label=tag&message=v2.5.0&color=0D5AF5">
</a>
<a href="https://github.com/oxygencobalt/Auxio/releases/">
<img alt="Releases" src="https://img.shields.io/github/downloads/OxygenCobalt/Auxio/total.svg">
@ -11,7 +11,7 @@
<a href="https://www.gnu.org/licenses/gpl-3.0">
<img src="https://img.shields.io/badge/license-GPL%20v3-blue.svg">
</a>
<img alt="Minimum SDK" src="https://img.shields.io/badge/API-21%2B-32B5ED">
<img alt="Minimum SDK Version" src="https://img.shields.io/badge/API-21%2B-32B5ED">
</p>
<h4 align="center"><a href="/CHANGELOG.md">Changelog</a> | <a href="/info/FAQ.md">FAQ</a> | <a href="/info/LICENSES.md">Licenses</a> | <a href="/.github/CONTRIBUTING.md">Contributing</a> | <a href="/info/ARCHITECTURE.md">Architecture</a></h4>
<p align="center">

View file

@ -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<String>): 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)
}
}

View file

@ -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<String>.parseReleaseType() = ReleaseType.parse(this)
/**
* Decodes the genre name from an ID3(v2) constant. See [GENRE_TABLE] for the genre constant map
* that Auxio uses.

View file

@ -170,7 +170,7 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) {
private fun completeAudio(metadata: Metadata) {
val id3v2Tags = mutableMapOf<String, String>()
val vorbisTags = mutableMapOf<String, String>()
val vorbisTags = mutableMapOf<String, MutableList<String>>()
// 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<String, String>) {
private fun populateVorbis(tags: Map<String, List<String>>) {
// (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 }