From 3834e92192fca45b4c99b7c5fddd0051ed929b57 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Mon, 24 Feb 2025 06:58:10 -0700 Subject: [PATCH] musikr: add backwards compat for v4 uids This annoying hack should be temporary once I can build a new UID system and migrate everything over. --- .../org/oxycblt/musikr/cache/CacheDatabase.kt | 4 +- .../org/oxycblt/musikr/graph/MusicGraph.kt | 2 +- .../org/oxycblt/musikr/model/LibraryImpl.kt | 4 +- .../java/org/oxycblt/musikr/model/SongImpl.kt | 5 +- .../oxycblt/musikr/pipeline/ExtractStep.kt | 2 +- .../oxycblt/musikr/tag/interpret/PreMusic.kt | 27 ++------- .../musikr/tag/interpret/TagInterpreter.kt | 56 ++++++++++++++++--- .../oxycblt/musikr/tag/parse/ParsedTags.kt | 2 +- .../org/oxycblt/musikr/tag/parse/TagParser.kt | 10 ++-- 9 files changed, 67 insertions(+), 45 deletions(-) diff --git a/musikr/src/main/java/org/oxycblt/musikr/cache/CacheDatabase.kt b/musikr/src/main/java/org/oxycblt/musikr/cache/CacheDatabase.kt index c4744c29e..d1700777c 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/cache/CacheDatabase.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/cache/CacheDatabase.kt @@ -41,7 +41,7 @@ import org.oxycblt.musikr.tag.parse.ParsedTags import org.oxycblt.musikr.util.correctWhitespace import org.oxycblt.musikr.util.splitEscaped -@Database(entities = [CachedSong::class], version = 58, exportSchema = false) +@Database(entities = [CachedSong::class], version = 60, exportSchema = false) internal abstract class CacheDatabase : RoomDatabase() { abstract fun visibleDao(): VisibleCacheDao @@ -97,7 +97,7 @@ internal data class CachedSong( val bitrateHz: Int, val sampleRateHz: Int, val musicBrainzId: String?, - val name: String, + val name: String?, val sortName: String?, val track: Int?, val disc: Int?, diff --git a/musikr/src/main/java/org/oxycblt/musikr/graph/MusicGraph.kt b/musikr/src/main/java/org/oxycblt/musikr/graph/MusicGraph.kt index 0d0e6104f..3bce98c69 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/graph/MusicGraph.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/graph/MusicGraph.kt @@ -55,7 +55,7 @@ private class MusicGraphBuilderImpl : MusicGraph.Builder { private val playlistVertices = mutableSetOf() override fun add(preSong: PreSong) { - val uid = preSong.uid + val uid = preSong.v363Uid if (songVertices.containsKey(uid)) { return } diff --git a/musikr/src/main/java/org/oxycblt/musikr/model/LibraryImpl.kt b/musikr/src/main/java/org/oxycblt/musikr/model/LibraryImpl.kt index badcada45..e60d4f662 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/model/LibraryImpl.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/model/LibraryImpl.kt @@ -37,6 +37,7 @@ internal data class LibraryImpl( private val playlistInterpreter: PlaylistInterpreter ) : MutableLibrary { private val songUidMap = songs.associateBy { it.uid } + private val v400SongUidMap = songs.associateBy { it.v400Uid } private val albumUidMap = albums.associateBy { it.uid } private val artistUidMap = artists.associateBy { it.uid } private val genreUidMap = genres.associateBy { it.uid } @@ -44,7 +45,8 @@ internal data class LibraryImpl( override fun empty() = songs.isEmpty() - override fun findSong(uid: Music.UID) = songUidMap[uid] + // Compat hack. See TagInterpreter for why this needs to be done + override fun findSong(uid: Music.UID) = songUidMap[uid] ?: v400SongUidMap[uid] override fun findSongByPath(path: Path) = songs.find { it.path == path } diff --git a/musikr/src/main/java/org/oxycblt/musikr/model/SongImpl.kt b/musikr/src/main/java/org/oxycblt/musikr/model/SongImpl.kt index 6a34168c6..383abf11c 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/model/SongImpl.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/model/SongImpl.kt @@ -42,7 +42,10 @@ internal interface SongCore { internal class SongImpl(private val handle: SongCore) : Song { private val preSong = handle.preSong - override val uid = preSong.uid + override val uid = preSong.v363Uid + + val v400Uid = preSong.v400Uid + override val name = preSong.name override val track = preSong.track override val disc = preSong.disc diff --git a/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt b/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt index 858421457..1d204fe5a 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt @@ -131,7 +131,7 @@ private class ExtractStepImpl( metadata .map { fileWith -> if (fileWith.with != null) { - val tags = tagParser.parse(fileWith.file, fileWith.with) + val tags = tagParser.parse(fileWith.with) val cover = fileWith.with.cover?.let { storedCovers.write(it) } RawSong(fileWith.file, fileWith.with.properties, tags, cover, addingMs) } else { diff --git a/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/PreMusic.kt b/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/PreMusic.kt index 1760039b1..d5722b0a6 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/PreMusic.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/PreMusic.kt @@ -29,9 +29,10 @@ import org.oxycblt.musikr.tag.Disc import org.oxycblt.musikr.tag.Name import org.oxycblt.musikr.tag.ReleaseType import org.oxycblt.musikr.tag.ReplayGainAdjustment -import org.oxycblt.musikr.util.update internal data class PreSong( + val v363Uid: Music.UID, + val v400Uid: Music.UID, val musicBrainzId: UUID?, val name: Name.Known, val rawName: String, @@ -51,28 +52,8 @@ internal data class PreSong( val cover: Cover?, val preAlbum: PreAlbum, val preArtists: List, - val preGenres: List, - val compatAlbumArtistNames: List -) { - val uid = - musicBrainzId?.let { Music.UID.musicBrainz(Music.UID.Item.SONG, it) } - ?: Music.UID.auxio(Music.UID.Item.SONG) { - // 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 - // same standard since grouping is already inherently linked to settings. - update(rawName) - update(preAlbum.rawName) - update(date) - - update(track) - update(disc?.number) - - update(preArtists.mapNotNull { artist -> artist.rawName }) - // We have to encode the album artist names as-is w/o the interpreter's actions to - // maintain UID parity - update(compatAlbumArtistNames) - } -} + val preGenres: List +) {} internal data class PreAlbum( val musicBrainzId: UUID?, diff --git a/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/TagInterpreter.kt b/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/TagInterpreter.kt index efab3b609..c74db7940 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/TagInterpreter.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/TagInterpreter.kt @@ -19,6 +19,8 @@ package org.oxycblt.musikr.tag.interpret import org.oxycblt.musikr.Interpretation +import org.oxycblt.musikr.Music +import org.oxycblt.musikr.fs.DeviceFile import org.oxycblt.musikr.fs.Format import org.oxycblt.musikr.pipeline.RawSong import org.oxycblt.musikr.tag.Disc @@ -29,6 +31,7 @@ import org.oxycblt.musikr.tag.ReplayGainAdjustment import org.oxycblt.musikr.tag.format.parseId3GenreNames import org.oxycblt.musikr.tag.parse.ParsedTags import org.oxycblt.musikr.util.toUuidOrNull +import org.oxycblt.musikr.util.update internal interface TagInterpreter { fun interpret(song: RawSong): PreSong @@ -53,13 +56,49 @@ private class TagInterpreterImpl(private val interpretation: Interpretation) : T song.tags.albumArtistSortNames, interpretation) val preAlbum = - makePreAlbum(song.tags, individualPreArtists, albumPreArtists, interpretation) + makePreAlbum( + song.tags, song.file, individualPreArtists, albumPreArtists, interpretation) val rawArtists = individualPreArtists.ifEmpty { albumPreArtists }.ifEmpty { listOf(unknownPreArtist()) } val rawGenres = makePreGenres(song.tags, interpretation).ifEmpty { listOf(unknownPreGenre()) } val uri = song.file.uri + + val songNameOrFile = song.tags.name ?: requireNotNull(song.file.path.name) + val songNameOrFileWithoutExt = + song.tags.name ?: requireNotNull(song.file.path.name).split('.').first() + val albumNameOrDir = song.tags.albumName ?: song.file.path.directory.name + val v363uid = + Music.UID.auxio(Music.UID.Item.SONG) { + update(songNameOrFileWithoutExt) + update(albumNameOrDir) + update(song.tags.date) + + update(song.tags.track) + update(song.tags.disc) + + update(song.tags.artistNames) + update(song.tags.albumArtistNames) + } + + // I was an idiot and accidentally changed the UID spec in v4.0.0, so we need to calculate + // the broken UID too and maintain compat for that version. + val v400uid = + Music.UID.auxio(Music.UID.Item.SONG) { + update(songNameOrFile) + update(song.tags.albumName) + update(song.tags.date) + + update(song.tags.track) + update(song.tags.disc) + + update(song.tags.artistNames.ifEmpty { listOf(null) }) + update(song.tags.albumArtistNames.ifEmpty { listOf(null) }) + } + return PreSong( + v363Uid = v363uid, + v400Uid = v400uid, uri = uri, path = song.file.path, size = song.file.size, @@ -67,8 +106,8 @@ private class TagInterpreterImpl(private val interpretation: Interpretation) : T modifiedMs = song.file.modifiedMs, addedMs = song.addedMs, musicBrainzId = song.tags.musicBrainzId?.toUuidOrNull(), - name = interpretation.naming.name(song.tags.name, song.tags.sortName), - rawName = song.tags.name, + name = interpretation.naming.name(songNameOrFileWithoutExt, song.tags.sortName), + rawName = songNameOrFileWithoutExt, track = song.tags.track, disc = song.tags.disc?.let { Disc(it, song.tags.subtitle) }, date = song.tags.date, @@ -83,22 +122,21 @@ private class TagInterpreterImpl(private val interpretation: Interpretation) : T preAlbum = preAlbum, preArtists = rawArtists, preGenres = rawGenres, - cover = song.cover, - compatAlbumArtistNames = song.tags.albumArtistNames) + cover = song.cover) } private fun makePreAlbum( parsedTags: ParsedTags, + deviceFile: DeviceFile, individualPreArtists: List, albumPreArtists: List, interpretation: Interpretation ): PreAlbum { + val name = parsedTags.albumName ?: deviceFile.path.directory.name return PreAlbum( musicBrainzId = parsedTags.albumMusicBrainzId?.toUuidOrNull(), - name = - interpretation.naming.name( - parsedTags.albumName, parsedTags.albumSortName, Placeholder.ALBUM), - rawName = parsedTags.albumName, + name = interpretation.naming.name(name, parsedTags.albumSortName, Placeholder.ALBUM), + rawName = name, releaseType = ReleaseType.parse(interpretation.separators.split(parsedTags.releaseTypes)) ?: ReleaseType.Album(null), diff --git a/musikr/src/main/java/org/oxycblt/musikr/tag/parse/ParsedTags.kt b/musikr/src/main/java/org/oxycblt/musikr/tag/parse/ParsedTags.kt index a7dc4d3c5..1d7198a56 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/tag/parse/ParsedTags.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/tag/parse/ParsedTags.kt @@ -25,7 +25,7 @@ internal data class ParsedTags( val replayGainTrackAdjustment: Float? = null, val replayGainAlbumAdjustment: Float? = null, val musicBrainzId: String? = null, - val name: String, + val name: String? = null, val sortName: String? = null, val track: Int? = null, val disc: Int? = null, diff --git a/musikr/src/main/java/org/oxycblt/musikr/tag/parse/TagParser.kt b/musikr/src/main/java/org/oxycblt/musikr/tag/parse/TagParser.kt index e3fe90f0f..a4dbda470 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/tag/parse/TagParser.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/tag/parse/TagParser.kt @@ -18,12 +18,10 @@ package org.oxycblt.musikr.tag.parse -import org.oxycblt.musikr.fs.DeviceFile import org.oxycblt.musikr.metadata.Metadata -import org.oxycblt.musikr.util.unlikelyToBeNull internal interface TagParser { - fun parse(file: DeviceFile, metadata: Metadata): ParsedTags + fun parse(metadata: Metadata): ParsedTags companion object { fun new(): TagParser = TagParserImpl @@ -31,21 +29,21 @@ internal interface TagParser { } private data object TagParserImpl : TagParser { - override fun parse(file: DeviceFile, metadata: Metadata): ParsedTags { + override fun parse(metadata: Metadata): ParsedTags { val compilation = metadata.isCompilation() return ParsedTags( durationMs = metadata.properties.durationMs, replayGainTrackAdjustment = metadata.replayGainTrackAdjustment(), replayGainAlbumAdjustment = metadata.replayGainAlbumAdjustment(), musicBrainzId = metadata.musicBrainzId(), - name = metadata.name() ?: unlikelyToBeNull(file.path.name).split('.').first(), + name = metadata.name(), sortName = metadata.sortName(), track = metadata.track(), disc = metadata.disc(), subtitle = metadata.subtitle(), date = metadata.date(), albumMusicBrainzId = metadata.albumMusicBrainzId(), - albumName = metadata.albumName() ?: file.path.directory.name, + albumName = metadata.albumName(), albumSortName = metadata.albumSortName(), // Compilation flag implies a compilation release type in the case that // we don't have any other release types