fix: Japanese/CJK albums vanished from artist discography (web + android)
The album-name normaliser stripped everything but a-z0-9, so any JP/CJK
title collapsed to "". All of an artist's Japanese singles then normalised
identically, de-duplicated into one entry and "matched" each other — e.g.
inabakumori (稲葉曇) showed only 2 of 9 releases. Normalisers now keep
Unicode letters/numbers (\p{L}\p{N}) in:
- web _normAlbum (detail.js)
- android normAlbum (categorizeAlbums), normTrack (partial-album split),
the enrichPartialCounts helpers, and the Deezer artist-name match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
ed641e999c
commit
026b392657
6 files changed
+21
-6
No files matched your search
@@ -106,6 +106,14 @@ like Android), concert events become **reliable across web/Android**, several An
|
|||||||
- **Android artist albums incomplete** — `lookupArtistAlbums` filtered to `record_type == "album"`,
|
- **Android artist albums incomplete** — `lookupArtistAlbums` filtered to `record_type == "album"`,
|
||||||
dropping EPs and singles that Deezer (and the web app) show. Now returns all, sorted album → EP →
|
dropping EPs and singles that Deezer (and the web app) show. Now returns all, sorted album → EP →
|
||||||
single.
|
single.
|
||||||
|
- **Japanese/CJK albums vanished from the artist discography (Web + Android)** — the album-name
|
||||||
|
normaliser stripped everything except `a-z0-9`, so any Japanese/CJK title collapsed to an empty
|
||||||
|
string. All of an artist's JP singles then normalised identically (`""`), so they de-duplicated
|
||||||
|
into one entry and even "matched" each other — e.g. inabakumori (稲葉曇) showed only 2 of 9 releases,
|
||||||
|
the rest hidden behind one album. The normaliser now keeps Unicode letters/numbers (`\p{L}\p{N}`),
|
||||||
|
so JP titles stay distinct and the full discography (In Library / Unvollständig / Noch nicht
|
||||||
|
heruntergeladen) shows correctly. Same fix applied to the partial-album track match and the
|
||||||
|
Deezer-artist name match.
|
||||||
- **Android "Unvollständig" (partial albums) never showed** — Deezer's `/artist/{id}/albums` omits
|
- **Android "Unvollständig" (partial albums) never showed** — Deezer's `/artist/{id}/albums` omits
|
||||||
`nb_tracks`, so an owned album's real total was unknown (0) and it never got flagged incomplete;
|
`nb_tracks`, so an owned album's real total was unknown (0) and it never got flagged incomplete;
|
||||||
every partly-downloaded album looked complete. The artist VM now resolves the real track count for
|
every partly-downloaded album looked complete. The artist VM now resolves the real track count for
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ import com.novasonic.app.ui.theme.*
|
|||||||
import com.novasonic.app.viewmodel.ItunesArtistState
|
import com.novasonic.app.viewmodel.ItunesArtistState
|
||||||
|
|
||||||
private fun normAlbum(s: String?): String =
|
private fun normAlbum(s: String?): String =
|
||||||
(s ?: "").lowercase().replace(Regex("[^a-z0-9\\s]"), " ").replace(Regex("\\s+"), " ").trim()
|
// Keep Unicode letters/numbers (\p{L}\p{N}) — stripping to a-z0-9 emptied every Japanese/CJK
|
||||||
|
// title, so all of an artist's JP singles collapsed into one and "matched" each other.
|
||||||
|
(s ?: "").lowercase().replace(Regex("[^\\p{L}\\p{N}\\s]"), " ").replace(Regex("\\s+"), " ").trim()
|
||||||
|
|
||||||
data class ArtistAlbumCategories(
|
data class ArtistAlbumCategories(
|
||||||
val downloaded: List<Pair<Album, ItunesItem?>>,
|
val downloaded: List<Pair<Album, ItunesItem?>>,
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ import com.novasonic.app.ui.theme.*
|
|||||||
import com.novasonic.app.viewmodel.PartialAlbumState
|
import com.novasonic.app.viewmodel.PartialAlbumState
|
||||||
|
|
||||||
private fun normTrack(s: String?) =
|
private fun normTrack(s: String?) =
|
||||||
(s ?: "").lowercase().replace(Regex("[^a-z0-9]"), "").trim()
|
// Keep Unicode letters/numbers so Japanese track titles aren't all emptied to "" (which made
|
||||||
|
// every downloaded JP track "match" every missing one in the partial-album split).
|
||||||
|
(s ?: "").lowercase().replace(Regex("[^\\p{L}\\p{N}]"), "").trim()
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PartialAlbumScreen(
|
fun PartialAlbumScreen(
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ class ArtistViewModel(app: Application, savedState: SavedStateHandle) : AndroidV
|
|||||||
// artists are fan-sorted (ItunesClient), so firstOrNull gives the most popular match.
|
// artists are fan-sorted (ItunesClient), so firstOrNull gives the most popular match.
|
||||||
// Prefer an EXACT normalized-name match: "Flay!" must not resolve to the far more popular
|
// Prefer an EXACT normalized-name match: "Flay!" must not resolve to the far more popular
|
||||||
// "K.FLAY" (its name merely contains "flay"). Only fall back to a loose contains-match.
|
// "K.FLAY" (its name merely contains "flay"). Only fall back to a loose contains-match.
|
||||||
fun norm(s: String?) = (s ?: "").lowercase().replace(Regex("[^a-z0-9]"), "")
|
fun norm(s: String?) = (s ?: "").lowercase().replace(Regex("[^\\p{L}\\p{N}]"), "")
|
||||||
val q = norm(artistName)
|
val q = norm(artistName)
|
||||||
val iArtist = (
|
val iArtist = (
|
||||||
artists.firstOrNull { norm(it.artistName) == q }
|
artists.firstOrNull { norm(it.artistName) == q }
|
||||||
@@ -198,7 +198,7 @@ class ArtistViewModel(app: Application, savedState: SavedStateHandle) : AndroidV
|
|||||||
val ndAlbums = _state.value.artist?.album ?: return
|
val ndAlbums = _state.value.artist?.album ?: return
|
||||||
val iAlbums = _state.value.iAlbums
|
val iAlbums = _state.value.iAlbums
|
||||||
if (ndAlbums.isEmpty() || iAlbums.isEmpty()) return
|
if (ndAlbums.isEmpty() || iAlbums.isEmpty()) return
|
||||||
fun norm(s: String?) = (s ?: "").lowercase().replace(Regex("[^a-z0-9]"), "")
|
fun norm(s: String?) = (s ?: "").lowercase().replace(Regex("[^\\p{L}\\p{N}]"), "")
|
||||||
val enriched = coroutineScope {
|
val enriched = coroutineScope {
|
||||||
iAlbums.map { ia ->
|
iAlbums.map { ia ->
|
||||||
val cid = ia.collectionId
|
val cid = ia.collectionId
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ class ExploreViewModel(app: Application) : AndroidViewModel(app) {
|
|||||||
ndAlbums: List<Album>, iAlbums: List<ItunesItem>,
|
ndAlbums: List<Album>, iAlbums: List<ItunesItem>,
|
||||||
): List<ItunesItem> {
|
): List<ItunesItem> {
|
||||||
if (ndAlbums.isEmpty() || iAlbums.isEmpty()) return iAlbums
|
if (ndAlbums.isEmpty() || iAlbums.isEmpty()) return iAlbums
|
||||||
fun norm(s: String?) = (s ?: "").lowercase().replace(Regex("[^a-z0-9]"), "")
|
fun norm(s: String?) = (s ?: "").lowercase().replace(Regex("[^\\p{L}\\p{N}]"), "")
|
||||||
var changed = false
|
var changed = false
|
||||||
val out = coroutineScope {
|
val out = coroutineScope {
|
||||||
iAlbums.map { ia ->
|
iAlbums.map { ia ->
|
||||||
|
|||||||
+4
-1
@@ -337,7 +337,10 @@ export async function loadArtistDetail(artist) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _normAlbum(s) {
|
function _normAlbum(s) {
|
||||||
return (s || '').toLowerCase().replace(/[^a-z0-9\s]/g, ' ').replace(/\s+/g, ' ').trim();
|
// Keep Unicode letters/numbers (not just a-z0-9) — otherwise every Japanese/CJK title strips to an
|
||||||
|
// empty string, so all of an artist's JP singles collapse into one entry and even "match" each
|
||||||
|
// other (that hid inabakumori's whole singles discography behind one album).
|
||||||
|
return (s || '').toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, ' ').replace(/\s+/g, ' ').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _renderArtistAlbums(el, ndAlbums, iAlbums, artist) {
|
async function _renderArtistAlbums(el, ndAlbums, iAlbums, artist) {
|
||||||
|
|||||||
Reference in new issue
Block a user