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:
parent
0212887610
commit
f206f08e79
5 changed files with 32 additions and 22 deletions
|
@ -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]
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 }
|
||||
|
|
Loading…
Reference in a new issue