Skip to content

fix(artists): properly handle artist conjunctions in YouTube Music API responses#3695

Open
kairosci wants to merge 5 commits intoMetrolistGroup:mainfrom
kairosci:feat/fix-author-page-issues
Open

fix(artists): properly handle artist conjunctions in YouTube Music API responses#3695
kairosci wants to merge 5 commits intoMetrolistGroup:mainfrom
kairosci:feat/fix-author-page-issues

Conversation

@kairosci
Copy link
Copy Markdown
Contributor

@kairosci kairosci commented May 3, 2026

Problem

Songs appear under wrong artist pages because the YouTube Music API returns multiple artists joined by conjunctions (e.g., "Artist1 & Artist2", "Artist1, Artist2", "Artist1 y Artist2") as a single string. The app was treating these as a single artist.

Cause

The artist parsing logic in ArtistPage.kt, ArtistItemsPage.kt, SearchSummaryPage.kt, and SearchSuggestionPage.kt did not split artist names by conjunction patterns. When artists like "Duki & LIT killah" were returned as a single run, the app created one artist with the combined name.

Solution

  • Added splitArtistsByConjunction() function in Runs.kt to split artist runs by conjunction patterns
  • Added localized and string to all metrolist_strings.xml files (60+ languages)
  • ArtistConjunctions.conjunction reads from R.string.and at app startup via App.kt
  • Applied splitArtistsByConjunction() to all relevant artist parsing locations:
    • ArtistPage.kt - fromMusicResponsiveListItemRenderer and fromMusicTwoRowItemRenderer
    • ArtistItemsPage.kt - both renderer functions
    • SearchSummaryPage.kt - fromMusicResponsiveListItemRenderer
    • SearchSuggestionPage.kt - fromMusicResponsiveListItemRenderer

Testing

  • innertube module compiles successfully with ./gradlew :innertube:compileDebugKotlin
  • Need manual testing on device to verify artist parsing works with various conjunction patterns ("&", ",", "y", "e", etc.)

Related Issues

  • Related to artist page issues

Summary by CodeRabbit

  • New Features

    • Improved artist parsing throughout the app to accurately handle multiple artists separated by conjunctions ("and"), commas, and ampersands in song listings, search results, and artist pages. This ensures more complete and accurate artist information is displayed.
  • Bug Fixes

    • Enhanced artist metadata extraction when names contain common separator characters.

kairosci added 5 commits May 3, 2026 13:46
…I responses

- Add splitArtistsByConjunction() function to Runs.kt to handle & and , separators in artist names
- Fix ArtistPage.kt to properly parse artists from MusicResponsiveListItemRenderer and MusicTwoRowItemRenderer
- Fix ArtistItemsPage.kt to properly parse artists from both renderer types
- Fix SearchSummaryPage.kt and SearchSuggestionPage.kt to handle artist conjunctions
- This resolves issues where songs appeared under wrong artists, artists had conjunction symbols in their names, and single-song artist pages were created incorrectly

The changes ensure that when YouTube Music returns "Artist1 & Artist2" or "Artist1, Artist2", each artist is properly extracted and associated with the song.
Add localized string for 'and' conjunction to support internationalized artist name parsing.
Revert to using only 'and' (via \band\b regex) for artist name
parsing. The localized conjunction strings in metrolist_strings.xml
will be used later for full internationalization.
…nctions

- ArtistConjunctions object holds the list of conjunction words
- splitArtistsByConjunction() now uses ArtistConjunctions.conjunctions
- App module can now set ArtistConjunctions.conjunctions with
  localized strings from metrolist_strings.xml at startup
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

📝 Walkthrough

Walkthrough

This PR introduces artist name conjunction splitting across music metadata parsers. A new string resource "and" is added, along with a configurable splitArtistsByConjunction() extension that parses artist runs by separator and conjunction tokens. Multiple page parsers are updated to use this new helper instead of oddElements() for extracting artist names.

Changes

Artist Conjunction Splitting

Layer / File(s) Summary
Configuration & Strings
app/src/main/res/values/metrolist_strings.xml, innertube/src/main/kotlin/com/metrolist/innertube/models/Runs.kt
Added conjunction_and string resource ("and") and ArtistConjunctions object with a configurable conjunctions list (defaulting to listOf("and")).
Core Parsing Logic
innertube/src/main/kotlin/com/metrolist/innertube/models/Runs.kt
Introduced List<Run>.splitArtistsByConjunction() extension that builds a conjunction regex, splits Run.text on matches, trims non-blank parts, and preserves navigationEndpoint only on the first split segment.
Integration: Page Parsers
innertube/src/main/kotlin/com/metrolist/innertube/pages/ArtistItemsPage.kt, innertube/src/main/kotlin/com/metrolist/innertube/pages/ArtistPage.kt, innertube/src/main/kotlin/com/metrolist/innertube/pages/SearchSuggestionPage.kt, innertube/src/main/kotlin/com/metrolist/innertube/pages/SearchSummaryPage.kt
Updated artist extraction in four page parsers: replaced oddElements()-based artist derivation with splitArtistsByConjunction() followed by filtering of blank/connector tokens (&, ,) and mapping to Artist objects with trimmed names and browse IDs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: fixing artist conjunction handling in YouTube Music API parsing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description follows the template structure with all required sections: Problem, Cause, Solution, Testing, and Related Issues clearly defined.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
innertube/src/main/kotlin/com/metrolist/innertube/pages/SearchSummaryPage.kt (1)

246-253: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

clean() can discard localized artist groups before the new splitter runs.

clean() in innertube/src/main/kotlin/com/metrolist/innertube/models/Runs.kt only keeps the first group when it has a browse endpoint or contains [&,]. For inputs like Artist1 y Artist2 / Artist1 e Artist2 with no linked artist runs, the artist block is dropped on Lines 246-253/326 before splitArtistsByConjunction() ever sees it, so this path still won't parse the localized cases the PR is targeting. SearchSuggestionPage.fromMusicResponsiveListItemRenderer has the same sequence.

Also applies to: 326-347

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@innertube/src/main/kotlin/com/metrolist/innertube/pages/SearchSummaryPage.kt`
around lines 246 - 253, clean() in Runs.kt is currently dropping groups that
lack a browse endpoint or an explicit "[&,]" token, which causes localized
artist conjunctions (e.g., "Artist1 y Artist2" / "Artist1 e Artist2") to be
removed before splitBySeparator()/splitArtistsByConjunction() run; update
clean() to preserve groups that contain localized conjunction patterns or
non-linked artist runs (or move the split logic earlier) so splitBySeparator()
and splitArtistsByConjunction() can operate on those raw runs—specifically,
modify the Runs.clean() logic to detect and retain runs containing conjunction
tokens (or defer trimming) and ensure SearchSummaryPage (and
SearchSuggestionPage.fromMusicResponsiveListItemRenderer) use the revised
clean/split sequence so secondaryLine parsing keeps localized artist groups.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@innertube/src/main/kotlin/com/metrolist/innertube/models/Runs.kt`:
- Line 34: conjunctionPattern in Runs.kt currently treats any comma as an
unconditional separator; remove the plain ", " alternative from the Regex("
$pattern |, | & ") and instead handle commas with a narrower, explicit rule:
keep conjunctionPattern to match " $pattern " and " & ", and add a targeted
comma-splitting step where you only split on ", " when the token after the comma
does not start with an article/common prefix (e.g., "The", "A", "An") and/or
when there are multiple comma-separated items (indicating a list); update the
code paths that call conjunctionPattern (the artist-splitting logic) to use this
new conditional comma-split so names like "Tyler, The Creator" are not split.

In `@innertube/src/main/kotlin/com/metrolist/innertube/pages/ArtistItemsPage.kt`:
- Around line 95-117: The current artist extraction filters out split segments
that have navigationEndpoint == null, then the fallback collapses to a single
artist; change the filter so plain-text segments produced by
splitArtistsByConjunction() are preserved: in the artist selection logic (the
artistRuns variable and the subsequent filter/map block) allow runs that either
have a browseEndpoint (browseId startsWith "UC" or browseEndpoint != null) OR
have no navigationEndpoint (navigationEndpoint == null) so they are mapped to
Artist(name = it.text.trim(), id = null); apply the same change in
ArtistPage.fromMusicTwoRowItemRenderer to keep unlinked split artists instead of
collapsing them to the first name.

---

Outside diff comments:
In
`@innertube/src/main/kotlin/com/metrolist/innertube/pages/SearchSummaryPage.kt`:
- Around line 246-253: clean() in Runs.kt is currently dropping groups that lack
a browse endpoint or an explicit "[&,]" token, which causes localized artist
conjunctions (e.g., "Artist1 y Artist2" / "Artist1 e Artist2") to be removed
before splitBySeparator()/splitArtistsByConjunction() run; update clean() to
preserve groups that contain localized conjunction patterns or non-linked artist
runs (or move the split logic earlier) so splitBySeparator() and
splitArtistsByConjunction() can operate on those raw runs—specifically, modify
the Runs.clean() logic to detect and retain runs containing conjunction tokens
(or defer trimming) and ensure SearchSummaryPage (and
SearchSuggestionPage.fromMusicResponsiveListItemRenderer) use the revised
clean/split sequence so secondaryLine parsing keeps localized artist groups.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6abfe804-86d7-460d-8984-4ba3baa2ba4b

📥 Commits

Reviewing files that changed from the base of the PR and between 6f3dfab and f083963.

📒 Files selected for processing (6)
  • app/src/main/res/values/metrolist_strings.xml
  • innertube/src/main/kotlin/com/metrolist/innertube/models/Runs.kt
  • innertube/src/main/kotlin/com/metrolist/innertube/pages/ArtistItemsPage.kt
  • innertube/src/main/kotlin/com/metrolist/innertube/pages/ArtistPage.kt
  • innertube/src/main/kotlin/com/metrolist/innertube/pages/SearchSuggestionPage.kt
  • innertube/src/main/kotlin/com/metrolist/innertube/pages/SearchSummaryPage.kt

fun List<Run>.splitArtistsByConjunction(): List<Run> {
val result = mutableListOf<Run>()
val pattern = ArtistConjunctions.conjunctions.joinToString("|") { Regex.escape(it) }
val conjunctionPattern = Regex(" $pattern |, | & ")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't treat commas as an unconditional artist separator.

Line 34 will split legitimate single-artist names like Tyler, The Creator into multiple artists, which is a worse misclassification than the one this PR is fixing. The shared helper needs a narrower heuristic for commas than , in the global regex.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@innertube/src/main/kotlin/com/metrolist/innertube/models/Runs.kt` at line 34,
conjunctionPattern in Runs.kt currently treats any comma as an unconditional
separator; remove the plain ", " alternative from the Regex(" $pattern |, | & ")
and instead handle commas with a narrower, explicit rule: keep
conjunctionPattern to match " $pattern " and " & ", and add a targeted
comma-splitting step where you only split on ", " when the token after the comma
does not start with an article/common prefix (e.g., "The", "A", "An") and/or
when there are multiple comma-separated items (indicating a list); update the
code paths that call conjunctionPattern (the artist-splitting logic) to use this
new conditional comma-split so names like "Tyler, The Creator" are not split.

Comment on lines +95 to +117
val subtitleRuns = renderer.subtitle?.runs ?: return null
// Split any runs that contain conjunctions (&, ,) to properly extract individual artists
val expandedRuns = subtitleRuns.splitArtistsByConjunction()
// Filter out separator runs (like "&", ",") to get only artist runs
val artistRuns = expandedRuns.filter {
it.text.isNotBlank() && it.text != "&" && it.text != ","
}
SongItem(
id = renderer.navigationEndpoint.watchEndpoint?.videoId ?: return null,
title = renderer.title.runs?.firstOrNull()?.text ?: return null,
artists = artistRuns.filter {
it.navigationEndpoint?.browseEndpoint?.browseId?.startsWith("UC") == true ||
it.navigationEndpoint?.browseEndpoint != null
}.map {
Artist(
name = it.text.trim(),
id = it.navigationEndpoint?.browseEndpoint?.browseId
)
}.ifEmpty {
artistRuns.firstOrNull()?.let {
listOf(Artist(name = it.text.trim(), id = null))
} ?: emptyList()
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Two-row songs still collapse unlinked split artists back to the first name.

After splitArtistsByConjunction(), any segment created from a plain text run has navigationEndpoint == null. The filter on Lines 105-108 removes those artists, and the fallback on Lines 113-116 keeps only the first one, so Artist1 & Artist2 still ends up as a single artist in this path. ArtistPage.fromMusicTwoRowItemRenderer has the same copied logic.

Suggested direction
-                    val subtitleRuns = renderer.subtitle?.runs ?: return null
-                    // Split any runs that contain conjunctions (&, ,) to properly extract individual artists
-                    val expandedRuns = subtitleRuns.splitArtistsByConjunction()
-                    // Filter out separator runs (like "&", ",") to get only artist runs
-                    val artistRuns = expandedRuns.filter { 
-                        it.text.isNotBlank() && it.text != "&" && it.text != "," 
-                    }
+                    val artistRuns = renderer.subtitle?.runs
+                        ?.splitBySeparator()
+                        ?.firstOrNull()
+                        .orEmpty()
+                        .splitArtistsByConjunction()
+                        .filter { it.text.isNotBlank() }
                     SongItem(
                         id = renderer.navigationEndpoint.watchEndpoint?.videoId ?: return null,
                         title = renderer.title.runs?.firstOrNull()?.text ?: return null,
-                        artists = artistRuns.filter { 
-                            it.navigationEndpoint?.browseEndpoint?.browseId?.startsWith("UC") == true ||
-                            it.navigationEndpoint?.browseEndpoint != null
-                        }.map {
+                        artists = artistRuns.map {
                             Artist(
                                 name = it.text.trim(),
                                 id = it.navigationEndpoint?.browseEndpoint?.browseId
                             )
-                        }.ifEmpty {
-                            artistRuns.firstOrNull()?.let { 
-                                listOf(Artist(name = it.text.trim(), id = null)) 
-                            } ?: emptyList()
-                        },
+                        }.ifEmpty { return null },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@innertube/src/main/kotlin/com/metrolist/innertube/pages/ArtistItemsPage.kt`
around lines 95 - 117, The current artist extraction filters out split segments
that have navigationEndpoint == null, then the fallback collapses to a single
artist; change the filter so plain-text segments produced by
splitArtistsByConjunction() are preserved: in the artist selection logic (the
artistRuns variable and the subsequent filter/map block) allow runs that either
have a browseEndpoint (browseId startsWith "UC" or browseEndpoint != null) OR
have no navigationEndpoint (navigationEndpoint == null) so they are mapped to
Artist(name = it.text.trim(), id = null); apply the same change in
ArtistPage.fromMusicTwoRowItemRenderer to keep unlinked split artists instead of
collapsing them to the first name.

@kairosci
Copy link
Copy Markdown
Contributor Author

kairosci commented May 3, 2026

[WIP]: I HAVEN'T TEST YET.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant