fix(metadata): keep collaboration albums grouped

This commit is contained in:
zarzet
2026-08-11 20:58:27 +07:00
parent 5374883875
commit efc03db308
6 changed files with 274 additions and 8 deletions
@@ -1068,7 +1068,14 @@ object NativeDownloadFinalizer {
.put("title", trackString(input, "name", input.request.optString("track_name", "")))
.put("artist", trackString(input, "artistName", input.request.optString("artist_name", "")))
.put("album", trackString(input, "albumName", input.request.optString("album_name", "")))
.put("album_artist", trackString(input, "albumArtist", input.request.optString("album_artist", "")))
.put(
"album_artist",
NativeFinalizationPolicy.authoritativeAlbumArtist(
requestValue = requestString(input, "album_artist"),
trackValue = trackString(input, "albumArtist", ""),
providerResultValue = resultString(input, "album_artist"),
),
)
.put("track_number", trackInt(input, "trackNumber", input.request.optInt("track_number", 0)))
.put("disc_number", trackInt(input, "discNumber", input.request.optInt("disc_number", 0)))
.put("isrc", trackString(input, "isrc", input.request.optString("isrc", "")))
@@ -1229,7 +1236,11 @@ object NativeDownloadFinalizer {
val albumId = trackString(input, "albumId", "")
if (albumId.isNotBlank()) return "id:$albumId"
val albumName = trackString(input, "albumName", input.request.optString("album_name", ""))
val albumArtist = trackString(input, "albumArtist", input.request.optString("album_artist", ""))
val albumArtist = NativeFinalizationPolicy.authoritativeAlbumArtist(
requestValue = requestString(input, "album_artist"),
trackValue = trackString(input, "albumArtist", ""),
providerResultValue = resultString(input, "album_artist"),
)
return "name:$albumName|$albumArtist"
}
@@ -1248,7 +1259,16 @@ object NativeDownloadFinalizer {
values.put("track_name", result.optString("title", "").ifBlank { trackString(input, "name", input.request.optString("track_name", "")) })
values.put("artist_name", result.optString("artist", "").ifBlank { trackString(input, "artistName", input.request.optString("artist_name", "")) })
values.put("album_name", result.optString("album", "").ifBlank { trackString(input, "albumName", input.request.optString("album_name", "")) })
values.put("album_artist", normalizeOptional(resultString(input, "album_artist").ifBlank { trackString(input, "albumArtist", requestString(input, "album_artist")) }))
values.put(
"album_artist",
normalizeOptional(
NativeFinalizationPolicy.authoritativeAlbumArtist(
requestValue = requestString(input, "album_artist"),
trackValue = trackString(input, "albumArtist", ""),
providerResultValue = resultString(input, "album_artist"),
),
),
)
values.put("cover_url", normalizeOptional(metadataCoverUrl(input).ifBlank { resultString(input, "cover_url") }))
values.put("file_path", state.filePath)
values.put("storage_mode", input.request.optString("storage_mode", "app"))
@@ -277,6 +277,22 @@ internal object NativeFinalizationPolicy {
return if (total > 0) "$number/$total" else number.toString()
}
/**
* The app request contains the album artist after batch normalization and
* user metadata filters. Provider results are only a fallback: preferring
* them would reintroduce per-track collaboration credits while finalizing.
*/
fun authoritativeAlbumArtist(
requestValue: String?,
trackValue: String?,
providerResultValue: String?,
): String {
return normalizeOptional(requestValue)
?: normalizeOptional(trackValue)
?: normalizeOptional(providerResultValue)
?: ""
}
private fun audioFormatForPath(filePath: String, fileName: String): String? {
for (candidate in listOf(filePath, fileName)) {
val lower = candidate.trim().lowercase(Locale.ROOT)
@@ -16,6 +16,7 @@ import com.antonkarpenko.ffmpegkit.ReturnCode
import com.zarz.spotiflac.SafDownloadHandler.mimeTypeForExt
import com.zarz.spotiflac.SafDownloadHandler.normalizeExt
import com.zarz.spotiflac.NativeFinalizationPolicy.applyQualityVariantFilenameLabel
import com.zarz.spotiflac.NativeFinalizationPolicy.authoritativeAlbumArtist
import com.zarz.spotiflac.NativeFinalizationPolicy.displayAudioQuality
import com.zarz.spotiflac.NativeFinalizationPolicy.formatIndexTag
import com.zarz.spotiflac.NativeFinalizationPolicy.isLosslessAudioCodec
@@ -257,9 +258,11 @@ internal fun NativeDownloadFinalizer.embedBasicMetadata(context: Context, path:
val album = resultString(input, "album").ifBlank {
trackString(input, "albumName", requestString(input, "album_name"))
}
val albumArtist = resultString(input, "album_artist").ifBlank {
trackString(input, "albumArtist", requestString(input, "album_artist"))
}
val albumArtist = authoritativeAlbumArtist(
requestValue = requestString(input, "album_artist"),
trackValue = trackString(input, "albumArtist", ""),
providerResultValue = resultString(input, "album_artist"),
)
val date = resultString(input, "release_date").ifBlank {
resultString(input, "date").ifBlank {
trackString(input, "releaseDate", requestString(input, "release_date"))
@@ -96,6 +96,26 @@ class NativeFinalizationPolicyTest {
)
}
@Test
fun normalizedRequestAlbumArtistOverridesPerTrackProviderCredits() {
assertEquals(
"Falling In Reverse",
NativeFinalizationPolicy.authoritativeAlbumArtist(
requestValue = "Falling In Reverse",
trackValue = "Falling In Reverse, Jelly Roll",
providerResultValue = "Falling In Reverse, Jelly Roll",
),
)
assertEquals(
"Provider Album Artist",
NativeFinalizationPolicy.authoritativeAlbumArtist(
requestValue = "",
trackValue = null,
providerResultValue = "Provider Album Artist",
),
)
}
@Test
fun displayQualityUsesMeasuredLosslessSpecifications() {
assertEquals(