Skip to content

feat(lyrics): implement multi-backend lyrics provider support#275

Open
yeonfish6040 wants to merge 10 commits into
sozercan:mainfrom
yeonfish6040:lyrics-backend-split
Open

feat(lyrics): implement multi-backend lyrics provider support#275
yeonfish6040 wants to merge 10 commits into
sozercan:mainfrom
yeonfish6040:lyrics-backend-split

Conversation

@yeonfish6040

Copy link
Copy Markdown

Description

Adds a multi-backend lyrics flow that supports automatic provider selection across multiple lyrics sources, while preserving manual provider selection for forcing a single backend. Also aligns the backend with pear-style YTMusic timed-lyrics payloads
and keeps provider switching from overwriting active lyrics with an unavailable state.

AI Prompt (Optional)

🤖 AI Prompt Used

N/A - Manual implementation

AI Tool: OpenAI Codex

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to change)
  • 📚 Documentation update
  • 🎨 UI/UX improvement
  • ♻️ Refactoring (no functional changes)
  • 🧪 Test update
  • 🔧 Build/CI configuration

Related Issues

Changes Made

  • Added multi-backend lyrics provider support with auto/manual selection behavior.
  • Kept auto mode concurrent across available providers.
  • Kept manual mode pinned to the selected provider.
  • Updated YTMusic parsing to follow the pear-style proxy payload and timed-lyrics structure.
  • Extended the parser to support both startTimeMs and cueRange.startTimeMilliseconds.
  • Broadened LRCLib and MusixMatch parsing to match the current response shapes.
  • Removed LyricsGenius from the supported provider set.
  • Prevented provider cycling from replacing synced lyrics with an unavailable state.
  • Added and updated tests for provider selection, parser coverage, and prefetch behavior.

Testing

  • Unit tests pass (xcodebuild test -only-testing:KasetTests)
  • Manual testing performed
  • UI tested on macOS 26+

Checklist

  • My code follows the project's style guidelines
  • I have run swiftlint --strict && swiftformat .
  • I have added tests that prove my fix/feature works
  • New and existing unit tests pass locally
  • I have updated documentation if needed
  • I have checked for any performance implications
  • My changes generate no new warnings

Screenshots

Additional Notes

# Conflicts:
#	Sources/Kaset/Services/SettingsManager.swift
- preserve the current lyrics result when a cycled provider returns unavailable\n- keep provider switching from replacing synced lyrics with an empty state\n- parse pear-style YTMusic proxy payloads and cueRange timestamps\n- broaden LRCLib and MusixMatch parsing to match the current backend payloads\n- remove LyricsGenius and update provider settings, tests, and fixtures\n- keep prefetch cache behavior and provider selection covered by tests
Copilot AI review requested due to automatic review settings May 28, 2026 23:07

Copilot AI left a comment

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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR introduces a MusixMatch lyrics provider, a "pear-style" browse proxy for synced YTMusic lyrics, a user-configurable default lyrics provider setting, the ability to cycle through providers in the lyrics view, and lyrics prefetching for adjacent queue tracks. The LyricsParser is updated to handle additional timed-lyrics payload shapes, and LRCLibProvider gains artist similarity filtering and album-based search.

Changes:

  • Adds MusixMatchProvider with macro API and HTML scraping, a configurable lyrics provider preference (SettingsManager.LyricsProviderPreference), and provider cycling UI in LyricsView.
  • Refactors SyncedLyricsService to support provider-specific fetching/caching, auto vs. manual mode, and adjacent-track prefetching driven by MainWindow.
  • Enhances LyricsParser to parse alternate timed-lyrics payload keys (e.g., cueRange, lyricLine) and routes browse requests through a proxy endpoint in YTMusicClient.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
Sources/Kaset/Services/Lyrics/Providers/MusixMatchProvider.swift New MusixMatch lyrics provider with API and HTML extraction
Sources/Kaset/Services/Lyrics/Providers/HTMLLyricsExtractor.swift New HTML parsing utility for lyrics extraction
Sources/Kaset/Services/Lyrics/Providers/LRCLibProvider.swift Adds artist similarity filtering, album query, and refactored duration matching
Sources/Kaset/Services/Lyrics/SyncedLyricsService.swift Adds provider-specific fetching, prefetching, caching, and auto/manual mode
Sources/Kaset/Services/API/Parsers/LyricsParser.swift Handles alternate timed-lyrics payload formats and hardcodes "YTMusic" source
Sources/Kaset/Services/API/YTMusicClient.swift Adds pear-style proxy endpoint for lyrics browse requests
Sources/Kaset/Services/SettingsManager.swift Adds LyricsProviderPreference enum and defaultLyricsProvider setting
Sources/Kaset/Views/LyricsView.swift Provider cycling UI, inline loading overlay, reloadLyrics refactor
Sources/Kaset/Views/MainWindow.swift Adjacent-track lyrics prefetching
Sources/Kaset/Views/GeneralSettingsView.swift Settings UI for default lyrics provider picker
Sources/Kaset/KasetApp.swift Registers MusixMatchProvider
Sources/Kaset/Services/API/Parsers/LyricsParser.swift Removes footer source extraction, hardcodes "YTMusic"
Tests/KasetTests/SyncedLyricsTests.swift Tests for provider preference, cycling, prefetching, pruning
Tests/KasetTests/LyricsParserTests.swift Tests for alternate payload keys and proxy fixture
Tests/KasetTests/Fixtures/lyrics_proxy_zzfYBupjwIc.json JSON fixture for proxy lyrics parsing test

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


if let statusCode = Self.statusCode(in: json), statusCode == 401 {
self.token = nil
self.tokenExpiresAt = nil
Comment on lines +50 to +51
guard let track = Self.track(from: macroCalls),
track.trackId != 115_264_642
Comment on lines +333 to +336
private var lyricsPrefetchKey: String {
let queueKey = self.playerService.queueEntryIDs.map(\.uuidString).joined(separator: ",")
let currentVideoId = self.playerService.currentTrack?.videoId ?? ""
return "\(currentVideoId)|\(self.playerService.currentIndex)|\(queueKey)"
}

self.token = try await self.fetchToken()
self.tokenExpiresAt = Date().addingTimeInterval(60)
private static func normalizeSearchText(_ text: String) -> String {
text.lowercased()
.replacingOccurrences(of: #"(?i)\s*[\(\[].*?[\)\]]"#, with: "", options: .regularExpression)
.replacingOccurrences(of: #"[^a-z0-9가-힣&]+"#, with: " ", options: .regularExpression)
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.

2 participants