music: add musicbrainz id support
Add support for MusicBrainz IDs (MBIDs) in both grouping and UID creation. This should help with very large libraries where artist names collide, thus requiring differentiation through other means. It also theoretically opens the door to fetch online metadata, however I don't really care for that and it would violate the non-connectivity promise of Auxio. Resolves #202.
This commit is contained in:
parent
5c76838f69
commit
b58fce9414
4 changed files with 105 additions and 63 deletions
|
@ -4,10 +4,11 @@
|
||||||
|
|
||||||
#### What's New
|
#### What's New
|
||||||
- Massively reworked music loading system:
|
- Massively reworked music loading system:
|
||||||
- Auxio now supports multiple artists
|
- Added support for multiple artists
|
||||||
- Auxio now supports multiple genres
|
- Added support for multiple genres
|
||||||
- Artists and album artists are now both given equal importance in the UI
|
- Artists and album artists are now both given UI entires
|
||||||
- Made music hashing rely on the more reliable MD5
|
- Made music hashing rely on the more reliable MD5
|
||||||
|
- Added support for MusicBrainz IDs (MBIDs)
|
||||||
- **This may impact your library.** Instructions on how to update your library to result in a good
|
- **This may impact your library.** Instructions on how to update your library to result in a good
|
||||||
artist experience will be added to the FAQ.
|
artist experience will be added to the FAQ.
|
||||||
|
|
||||||
|
|
|
@ -160,17 +160,15 @@ sealed class Music : Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
val mode = MusicMode.fromInt(ids[0].toIntOrNull(16) ?: return null) ?: return null
|
val mode = MusicMode.fromInt(ids[0].toIntOrNull(16) ?: return null) ?: return null
|
||||||
val uuid = UUID.fromString(ids[1])
|
val uuid = ids[1].toUuidOrNull() ?: return null
|
||||||
|
|
||||||
return UID(format, mode, uuid)
|
return UID(format, mode, uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a UUID derived from the MD5 hash of the data digested in [updates].
|
* Make a UUID derived from the MD5 hash of the data digested in [updates].
|
||||||
*
|
|
||||||
* This is Auxio's UID format.
|
|
||||||
*/
|
*/
|
||||||
fun hashed(mode: MusicMode, updates: MessageDigest.() -> Unit): UID {
|
fun auxio(mode: MusicMode, updates: MessageDigest.() -> Unit): UID {
|
||||||
// Auxio hashes consist of the MD5 hash of the non-subjective, consistent
|
// Auxio hashes consist of the MD5 hash of the non-subjective, consistent
|
||||||
// tags in a music item. For easier use with MusicBrainz IDs, we transform
|
// tags in a music item. For easier use with MusicBrainz IDs, we transform
|
||||||
// this into a UUID too.
|
// this into a UUID too.
|
||||||
|
@ -179,6 +177,12 @@ sealed class Music : Item {
|
||||||
val uuid = digest.digest().toUuid()
|
val uuid = digest.digest().toUuid()
|
||||||
return UID(Format.AUXIO, mode, uuid)
|
return UID(Format.AUXIO, mode, uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a UUID derived from a MusicBrainz ID.
|
||||||
|
*/
|
||||||
|
fun musicBrainz(mode: MusicMode, uuid: UUID): UID =
|
||||||
|
UID(Format.MUSICBRAINZ, mode, uuid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +207,7 @@ sealed class MusicParent : Music() {
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class Song constructor(raw: Raw, settings: Settings) : Music() {
|
class Song constructor(raw: Raw, settings: Settings) : Music() {
|
||||||
override val uid = UID.hashed(MusicMode.SONGS) {
|
override val uid = raw.musicBrainzId?.toUuidOrNull()?.let { UID.musicBrainz(MusicMode.SONGS, it) } ?: UID.auxio(MusicMode.SONGS) {
|
||||||
// Song UIDs are based on the raw data without parsing so that they remain
|
// Song UIDs are based on the raw data without parsing so that they remain
|
||||||
// consistent across music setting changes. Parents are not held up to the
|
// consistent across music setting changes. Parents are not held up to the
|
||||||
// same standard since grouping is already inherently linked to settings.
|
// same standard since grouping is already inherently linked to settings.
|
||||||
|
@ -273,20 +277,24 @@ class Song constructor(raw: Raw, settings: Settings) : Music() {
|
||||||
val album: Album
|
val album: Album
|
||||||
get() = unlikelyToBeNull(_album)
|
get() = unlikelyToBeNull(_album)
|
||||||
|
|
||||||
|
private val artistMusicBrainzIds = raw.artistMusicBrainzIds.parseMultiValue(settings)
|
||||||
|
|
||||||
private val artistNames = raw.artistNames.parseMultiValue(settings)
|
private val artistNames = raw.artistNames.parseMultiValue(settings)
|
||||||
|
|
||||||
private val albumArtistNames = raw.albumArtistNames.parseMultiValue(settings)
|
|
||||||
|
|
||||||
private val artistSortNames = raw.artistSortNames.parseMultiValue(settings)
|
private val artistSortNames = raw.artistSortNames.parseMultiValue(settings)
|
||||||
|
|
||||||
|
private val albumArtistMusicBrainzIds = raw.albumArtistMusicBrainzIds.parseMultiValue(settings)
|
||||||
|
|
||||||
|
private val albumArtistNames = raw.albumArtistNames.parseMultiValue(settings)
|
||||||
|
|
||||||
private val albumArtistSortNames = raw.albumArtistSortNames.parseMultiValue(settings)
|
private val albumArtistSortNames = raw.albumArtistSortNames.parseMultiValue(settings)
|
||||||
|
|
||||||
private val rawArtists = artistNames.mapIndexed { i, name ->
|
private val rawArtists = artistNames.mapIndexed { i, name ->
|
||||||
Artist.Raw(name, artistSortNames.getOrNull(i))
|
Artist.Raw(artistMusicBrainzIds.getOrNull(i)?.toUuidOrNull(), name, artistSortNames.getOrNull(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
private val rawAlbumArtists = albumArtistNames.mapIndexed { i, name ->
|
private val rawAlbumArtists = albumArtistNames.mapIndexed { i, name ->
|
||||||
Artist.Raw(name, albumArtistSortNames.getOrNull(i))
|
Artist.Raw(albumArtistMusicBrainzIds.getOrNull(i)?.toUuidOrNull(), name, albumArtistSortNames.getOrNull(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _artists = mutableListOf<Artist>()
|
private val _artists = mutableListOf<Artist>()
|
||||||
|
@ -339,15 +347,16 @@ class Song constructor(raw: Raw, settings: Settings) : Music() {
|
||||||
// --- INTERNAL FIELDS ---
|
// --- INTERNAL FIELDS ---
|
||||||
|
|
||||||
val _rawGenres = raw.genreNames.parseId3GenreNames(settings)
|
val _rawGenres = raw.genreNames.parseId3GenreNames(settings)
|
||||||
.map { Genre.Raw(it) }.ifEmpty { listOf(Genre.Raw(null)) }
|
.map { Genre.Raw(it) }.ifEmpty { listOf(Genre.Raw()) }
|
||||||
|
|
||||||
val _rawArtists = rawArtists.ifEmpty { rawAlbumArtists }.ifEmpty {
|
val _rawArtists = rawArtists.ifEmpty { rawAlbumArtists }.ifEmpty {
|
||||||
listOf(Artist.Raw(null, null))
|
listOf(Artist.Raw())
|
||||||
}
|
}
|
||||||
|
|
||||||
val _rawAlbum =
|
val _rawAlbum =
|
||||||
Album.Raw(
|
Album.Raw(
|
||||||
mediaStoreId = requireNotNull(raw.albumMediaStoreId) { "Invalid raw: No album id" },
|
mediaStoreId = requireNotNull(raw.albumMediaStoreId) { "Invalid raw: No album id" },
|
||||||
|
musicBrainzId = raw.albumMusicBrainzId?.toUuidOrNull(),
|
||||||
name = requireNotNull(raw.albumName) { "Invalid raw: No album name" },
|
name = requireNotNull(raw.albumName) { "Invalid raw: No album name" },
|
||||||
sortName = raw.albumSortName,
|
sortName = raw.albumSortName,
|
||||||
releaseType = raw.albumReleaseType.parseReleaseType(settings),
|
releaseType = raw.albumReleaseType.parseReleaseType(settings),
|
||||||
|
@ -377,7 +386,7 @@ class Song constructor(raw: Raw, settings: Settings) : Music() {
|
||||||
class Raw
|
class Raw
|
||||||
constructor(
|
constructor(
|
||||||
var mediaStoreId: Long? = null,
|
var mediaStoreId: Long? = null,
|
||||||
var mbid: UUID? = null,
|
var musicBrainzId: String? = null,
|
||||||
var name: String? = null,
|
var name: String? = null,
|
||||||
var sortName: String? = null,
|
var sortName: String? = null,
|
||||||
var displayName: String? = null,
|
var displayName: String? = null,
|
||||||
|
@ -392,14 +401,14 @@ class Song constructor(raw: Raw, settings: Settings) : Music() {
|
||||||
var disc: Int? = null,
|
var disc: Int? = null,
|
||||||
var date: Date? = null,
|
var date: Date? = null,
|
||||||
var albumMediaStoreId: Long? = null,
|
var albumMediaStoreId: Long? = null,
|
||||||
var albumMbid: UUID? = null,
|
var albumMusicBrainzId: String? = null,
|
||||||
var albumName: String? = null,
|
var albumName: String? = null,
|
||||||
var albumSortName: String? = null,
|
var albumSortName: String? = null,
|
||||||
var albumReleaseType: List<String> = listOf(),
|
var albumReleaseType: List<String> = listOf(),
|
||||||
var artistMbids: List<UUID> = listOf(),
|
var artistMusicBrainzIds: List<String> = listOf(),
|
||||||
var artistNames: List<String> = listOf(),
|
var artistNames: List<String> = listOf(),
|
||||||
var artistSortNames: List<String> = listOf(),
|
var artistSortNames: List<String> = listOf(),
|
||||||
var albumArtistMbids: List<UUID> = listOf(),
|
var albumArtistMusicBrainzIds: List<String> = listOf(),
|
||||||
var albumArtistNames: List<String> = listOf(),
|
var albumArtistNames: List<String> = listOf(),
|
||||||
var albumArtistSortNames: List<String> = listOf(),
|
var albumArtistSortNames: List<String> = listOf(),
|
||||||
var genreNames: List<String> = listOf()
|
var genreNames: List<String> = listOf()
|
||||||
|
@ -411,7 +420,8 @@ class Song constructor(raw: Raw, settings: Settings) : Music() {
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class Album constructor(raw: Raw, override val songs: List<Song>) : MusicParent() {
|
class Album constructor(raw: Raw, override val songs: List<Song>) : MusicParent() {
|
||||||
override val uid = UID.hashed(MusicMode.ALBUMS) {
|
override val uid = raw.musicBrainzId?.let { UID.musicBrainz(MusicMode.ALBUMS, it) }
|
||||||
|
?: UID.auxio(MusicMode.ALBUMS) {
|
||||||
// Hash based on only names despite the presence of a date to increase stability.
|
// Hash based on only names despite the presence of a date to increase stability.
|
||||||
// I don't know if there is any situation where an artist will have two albums with
|
// I don't know if there is any situation where an artist will have two albums with
|
||||||
// the exact same name, but if there is, I would love to know.
|
// the exact same name, but if there is, I would love to know.
|
||||||
|
@ -517,17 +527,27 @@ class Album constructor(raw: Raw, override val songs: List<Song>) : MusicParent(
|
||||||
|
|
||||||
class Raw(
|
class Raw(
|
||||||
val mediaStoreId: Long,
|
val mediaStoreId: Long,
|
||||||
|
val musicBrainzId: UUID?,
|
||||||
val name: String,
|
val name: String,
|
||||||
val sortName: String?,
|
val sortName: String?,
|
||||||
val releaseType: ReleaseType?,
|
val releaseType: ReleaseType?,
|
||||||
val rawArtists: List<Artist.Raw>
|
val rawArtists: List<Artist.Raw>
|
||||||
) {
|
) {
|
||||||
private val hashCode = 31 * name.lowercase().hashCode() + rawArtists.hashCode()
|
private val hashCode =
|
||||||
|
musicBrainzId?.hashCode() ?: (31 * name.lowercase().hashCode() + rawArtists.hashCode())
|
||||||
|
|
||||||
override fun hashCode() = hashCode
|
override fun hashCode() = hashCode
|
||||||
|
|
||||||
override fun equals(other: Any?) =
|
override fun equals(other: Any?): Boolean {
|
||||||
other is Raw && name.equals(other.name, true) && rawArtists == other.rawArtists
|
if (other !is Raw) return false
|
||||||
|
if (musicBrainzId != null && other.musicBrainzId != null &&
|
||||||
|
musicBrainzId == other.musicBrainzId
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return name.equals(other.name, true) && rawArtists == other.rawArtists
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -538,7 +558,7 @@ class Album constructor(raw: Raw, override val songs: List<Song>) : MusicParent(
|
||||||
*/
|
*/
|
||||||
class Artist
|
class Artist
|
||||||
constructor(raw: Raw, songAlbums: List<Music>) : MusicParent() {
|
constructor(raw: Raw, songAlbums: List<Music>) : MusicParent() {
|
||||||
override val uid = UID.hashed(MusicMode.ARTISTS) { update(raw.name) }
|
override val uid = raw.musicBrainzId?.let { UID.musicBrainz(MusicMode.ARTISTS, it) } ?: UID.auxio(MusicMode.ARTISTS) { update(raw.name) }
|
||||||
|
|
||||||
override val rawName = raw.name
|
override val rawName = raw.name
|
||||||
|
|
||||||
|
@ -615,27 +635,35 @@ constructor(raw: Raw, songAlbums: List<Music>) : MusicParent() {
|
||||||
.sortedByDescending { genre -> songs.count { it.genres.contains(genre) } }
|
.sortedByDescending { genre -> songs.count { it.genres.contains(genre) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
class Raw(val name: String?, val sortName: String?) {
|
class Raw(val musicBrainzId: UUID? = null, val name: String? = null, val sortName: String? = null) {
|
||||||
private val hashCode = name?.lowercase().hashCode()
|
private val hashCode = musicBrainzId?.hashCode() ?: name?.lowercase().hashCode()
|
||||||
|
|
||||||
override fun hashCode() = hashCode
|
override fun hashCode() = hashCode
|
||||||
|
|
||||||
override fun equals(other: Any?) =
|
override fun equals(other: Any?): Boolean {
|
||||||
other is Raw &&
|
if (other !is Raw) return false
|
||||||
when {
|
|
||||||
|
if (musicBrainzId != null && other.musicBrainzId != null &&
|
||||||
|
musicBrainzId == other.musicBrainzId
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return when {
|
||||||
name != null && other.name != null -> name.equals(other.name, true)
|
name != null && other.name != null -> name.equals(other.name, true)
|
||||||
name == null && other.name == null -> true
|
name == null && other.name == null -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A genre.
|
* A genre.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class Genre constructor(raw: Raw, override val songs: List<Song>) : MusicParent() {
|
class Genre constructor(raw: Raw, override val songs: List<Song>) : MusicParent() {
|
||||||
override val uid = UID.hashed(MusicMode.GENRES) { update(raw.name) }
|
override val uid = UID.auxio(MusicMode.GENRES) { update(raw.name) }
|
||||||
|
|
||||||
override val rawName = raw.name
|
override val rawName = raw.name
|
||||||
|
|
||||||
|
@ -674,7 +702,7 @@ class Genre constructor(raw: Raw, override val songs: List<Song>) : MusicParent(
|
||||||
check(songs.isNotEmpty()) { "Malformed genre: Empty" }
|
check(songs.isNotEmpty()) { "Malformed genre: Empty" }
|
||||||
}
|
}
|
||||||
|
|
||||||
class Raw(val name: String?) {
|
class Raw(val name: String? = null) {
|
||||||
private val hashCode = name?.lowercase().hashCode()
|
private val hashCode = name?.lowercase().hashCode()
|
||||||
|
|
||||||
override fun hashCode() = hashCode
|
override fun hashCode() = hashCode
|
||||||
|
|
|
@ -26,6 +26,7 @@ import android.provider.MediaStore
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
/** Shortcut for making a [ContentResolver] query with less superfluous arguments. */
|
/** Shortcut for making a [ContentResolver] query with less superfluous arguments. */
|
||||||
fun ContentResolver.queryCursor(
|
fun ContentResolver.queryCursor(
|
||||||
|
@ -58,6 +59,12 @@ val Long.audioUri: Uri
|
||||||
val Long.albumCoverUri: Uri
|
val Long.albumCoverUri: Uri
|
||||||
get() = ContentUris.withAppendedId(EXTERNAL_ALBUM_ART_URI, this)
|
get() = ContentUris.withAppendedId(EXTERNAL_ALBUM_ART_URI, this)
|
||||||
|
|
||||||
|
fun String.toUuidOrNull(): UUID? = try {
|
||||||
|
UUID.fromString(this)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
/** Shortcut to resolve a year from a nullable date. Will return "No Date" if it is null. */
|
/** Shortcut to resolve a year from a nullable date. Will return "No Date" if it is null. */
|
||||||
fun Date?.resolveYear(context: Context) =
|
fun Date?.resolveYear(context: Context) =
|
||||||
this?.resolveYear(context) ?: context.getString(R.string.def_date)
|
this?.resolveYear(context) ?: context.getString(R.string.def_date)
|
||||||
|
|
|
@ -41,6 +41,8 @@ import org.oxycblt.auxio.util.logW
|
||||||
* pitfalls given ExoPlayer's cozy relationship with native code. However, this backend should do
|
* pitfalls given ExoPlayer's cozy relationship with native code. However, this backend should do
|
||||||
* enough to eliminate such issues.
|
* enough to eliminate such issues.
|
||||||
*
|
*
|
||||||
|
* TODO: Fix failing ID3v2 multi-value tests in fork (Implies parsing problem)
|
||||||
|
*
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class MetadataExtractor(private val context: Context, private val mediaStoreExtractor: MediaStoreExtractor) {
|
class MetadataExtractor(private val context: Context, private val mediaStoreExtractor: MediaStoreExtractor) {
|
||||||
|
@ -193,7 +195,8 @@ class Task(context: Context, private val raw: Song.Raw) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun populateId3v2(tags: Map<String, List<String>>) {
|
private fun populateId3v2(tags: Map<String, List<String>>) {
|
||||||
// (Sort) Title
|
// Song
|
||||||
|
tags["TXXX:MusicBrainz Release Track Id"]?.let { raw.musicBrainzId = it[0] }
|
||||||
tags["TIT2"]?.let { raw.name = it[0] }
|
tags["TIT2"]?.let { raw.name = it[0] }
|
||||||
tags["TSOT"]?.let { raw.sortName = it[0] }
|
tags["TSOT"]?.let { raw.sortName = it[0] }
|
||||||
|
|
||||||
|
@ -219,25 +222,26 @@ class Task(context: Context, private val raw: Song.Raw) {
|
||||||
)
|
)
|
||||||
?.let { raw.date = it }
|
?.let { raw.date = it }
|
||||||
|
|
||||||
// (Sort) Album
|
// Album
|
||||||
|
tags["TXXX:MusicBrainz Album Id"]?.let { raw.albumMusicBrainzId = it[0] }
|
||||||
tags["TALB"]?.let { raw.albumName = it[0] }
|
tags["TALB"]?.let { raw.albumName = it[0] }
|
||||||
tags["TSOA"]?.let { raw.albumSortName = it[0] }
|
tags["TSOA"]?.let { raw.albumSortName = it[0] }
|
||||||
|
|
||||||
// (Sort) Artist
|
|
||||||
(tags["TXXX:ARTISTS"] ?: tags["TPE1"])?.let { raw.artistNames = it }
|
|
||||||
tags["TSOP"]?.let { raw.artistSortNames = it }
|
|
||||||
|
|
||||||
// (Sort) Album artist
|
|
||||||
tags["TPE2"]?.let { raw.albumArtistNames = it }
|
|
||||||
tags["TSO2"]?.let { raw.albumArtistSortNames = it }
|
|
||||||
|
|
||||||
// Genre, with the weird ID3 rules.
|
|
||||||
tags["TCON"]?.let { raw.genreNames = it }
|
|
||||||
|
|
||||||
// Release type (GRP1 is sometimes used for this, so fall back to it)
|
|
||||||
(tags["TXXX:MusicBrainz Album Type"] ?: tags["GRP1"])?.let {
|
(tags["TXXX:MusicBrainz Album Type"] ?: tags["GRP1"])?.let {
|
||||||
raw.albumReleaseType = it
|
raw.albumReleaseType = it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Artist
|
||||||
|
tags["TXXX:MusicBrainz Artist Id"]?.let { raw.artistMusicBrainzIds = it }
|
||||||
|
(tags["TXXX:ARTISTS"] ?: tags["TPE1"])?.let { raw.artistNames = it }
|
||||||
|
tags["TSOP"]?.let { raw.artistSortNames = it }
|
||||||
|
|
||||||
|
// Album artist
|
||||||
|
tags["TXXX:MusicBrainz Album Artist Id"]?.let { raw.albumArtistMusicBrainzIds = it }
|
||||||
|
tags["TPE2"]?.let { raw.albumArtistNames = it }
|
||||||
|
tags["TSO2"]?.let { raw.albumArtistSortNames = it }
|
||||||
|
|
||||||
|
// Genre
|
||||||
|
tags["TCON"]?.let { raw.genreNames = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseId3v23Date(tags: Map<String, List<String>>): Date? {
|
private fun parseId3v23Date(tags: Map<String, List<String>>): Date? {
|
||||||
|
@ -267,7 +271,8 @@ class Task(context: Context, private val raw: Song.Raw) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun populateVorbis(tags: Map<String, List<String>>) {
|
private fun populateVorbis(tags: Map<String, List<String>>) {
|
||||||
// (Sort) Title
|
// Song
|
||||||
|
tags["MUSICBRAINZ_RELEASETRACKID"]?.let { raw.musicBrainzId = it[0] }
|
||||||
tags["TITLE"]?.let { raw.name = it[0] }
|
tags["TITLE"]?.let { raw.name = it[0] }
|
||||||
tags["TITLESORT"]?.let { raw.sortName = it[0] }
|
tags["TITLESORT"]?.let { raw.sortName = it[0] }
|
||||||
|
|
||||||
|
@ -290,23 +295,24 @@ class Task(context: Context, private val raw: Song.Raw) {
|
||||||
)
|
)
|
||||||
?.let { raw.date = it }
|
?.let { raw.date = it }
|
||||||
|
|
||||||
// (Sort) Album
|
// Album
|
||||||
|
tags["MUSICBRAINZ_ALBUMID"]?.let { raw.albumMusicBrainzId = it[0] }
|
||||||
tags["ALBUM"]?.let { raw.albumName = it[0] }
|
tags["ALBUM"]?.let { raw.albumName = it[0] }
|
||||||
tags["ALBUMSORT"]?.let { raw.albumSortName = it[0] }
|
tags["ALBUMSORT"]?.let { raw.albumSortName = it[0] }
|
||||||
|
tags["RELEASETYPE"]?.let { raw.albumReleaseType = it }
|
||||||
|
|
||||||
// (Sort) Artist
|
// Artist
|
||||||
|
tags["MUSICBRAINZ_ARTISTID"]?.let { raw.artistMusicBrainzIds = it }
|
||||||
tags["ARTIST"]?.let { raw.artistNames = it }
|
tags["ARTIST"]?.let { raw.artistNames = it }
|
||||||
tags["ARTISTSORT"]?.let { raw.artistSortNames = it }
|
tags["ARTISTSORT"]?.let { raw.artistSortNames = it }
|
||||||
|
|
||||||
// (Sort) Album artist
|
// Album artist
|
||||||
|
tags["MUSICBRAINZ_ALBUMARTISTID"]?.let { raw.albumArtistMusicBrainzIds = it }
|
||||||
tags["ALBUMARTIST"]?.let { raw.albumArtistNames = it }
|
tags["ALBUMARTIST"]?.let { raw.albumArtistNames = it }
|
||||||
tags["ALBUMARTISTSORT"]?.let { raw.albumArtistSortNames = it }
|
tags["ALBUMARTISTSORT"]?.let { raw.albumArtistSortNames = it }
|
||||||
|
|
||||||
// Genre, no ID3 rules here
|
// Genre
|
||||||
tags["GENRE"]?.let { raw.genreNames = it }
|
tags["GENRE"]?.let { raw.genreNames = it }
|
||||||
|
|
||||||
// Release type
|
|
||||||
tags["RELEASETYPE"]?.let { raw.albumReleaseType = it }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue