Add Apple Music as a first-class lyric source via MusicKit#191
Open
koto9x wants to merge 11 commits into
Open
Conversation
Required usage description string for MusicKit authorization on macOS.
TDD: 3 TTML fixtures (synced/plain/malformed) + failing tests first, then XMLDocument-based parser with HH:MM:SS.mmm timecode parsing. All 3 TTMLParserTests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ns [], span fixture) - TTMLParserError now Equatable; malformed test asserts .invalidXML specifically - Empty TTML (no <p>) returns [] instead of throwing .missingBody (caller-chain falls through naturally); removed unused .missingBody case - New ttml-synced-spans.ttml fixture mirrors Apple's word-level <span> children with ttm:agent attributes; new test verifies stringValue assembles line text correctly with no XML leakage (no parser changes needed — XMLElement.stringValue concatenates child text nodes + Apple's inter-span whitespace) - Renamed local startMS -> startTimeMS; documented ms-vs-seconds convention - Expanded parseTimecode switch cases to multi-line - XCTUnwrap in fixture helper (no more force-unwraps) - Doc comment: "static block" -> "unsynced block" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Lightweight CoreData migration. Adds optional appleMusicID (String?) to SongObject so fetched lyrics can be keyed by Apple Music catalog ID (Adam ID) in addition to the existing Spotify trackID. This enables cross-player cache reuse and catalog-ID-based lookup for the AM provider. New fields: appleMusicID (optional String)
- Pin koto9x/mediaremote-adapter to 6825821 (feat/expose-adam-ids branch)
which adds contentItemIdentifier + albumiTunesStoreAdamIdentifier to
TrackInfo.Payload, plus the missing CFSTR definitions that fix the linker.
- Add lastObservedCatalogID / lastObservedAlbumCatalogID vars to
AppleMusicPlayer, populated from the MediaRemote payload in ViewModel's
onTrackInfoReceived callback.
- Fix MediaController() call site: bundleIdentifier param was removed in
adapter commit b8ce5d1 ("remove broken bundle ID filtering").
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nd cache Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ext) A sheet attached to a MenuBarExtra label view is a silent no-op on macOS — the label has no NSWindow host. Route the auth trigger through an openWindow(id:) call to a dedicated Window scene, matching the existing onboarding/search/update window pattern. The auth trigger + denied alert are pulled into an AppleMusicAuthModifier to keep the MenuBarExtra label's modifier chain (and thereby the @SceneBuilder body) inside SwiftUI's type-check budget. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6 tasks
koto9x
added a commit
to koto9x/LyricFever
that referenced
this pull request
Jul 4, 2026
Upstream PRs: - A: koto9x:upstream-pr-a-applemusic → aviwad#191 Title: Add Apple Music as a first-class lyric source via MusicKit Commits: TTMLParser, AppleMusicAuthManager, AppleMusicAuthView, schema v2 (appleMusicID only), catalog ID capture (Task 6), AppleMusicLyricProvider, ViewModel chain reorder, auth sheet trigger + Window scene fix - B: koto9x:upstream-pr-b-sticky-picks → aviwad#192 Title: Make SearchWindow manual picks sticky + add 'Reset lyrics' menu item Commits: schema v2 (userPicked+sourceProvider only), ViewModel sticky read path, SearchWindow apply, resetLyricsForCurrentTrack + menubar button - C: koto9x:upstream-pr-c-album-prefetch → aviwad#193 Title: Optional: prefetch lyrics for every track on the currently-playing album Commits: schema v3 (albumID), AppleMusicPrefetcher.warmAlbum, prefetchAlbumLyrics toggle (DEFAULT OFF), ViewModel gating Adapter dependency: ejbills/mediaremote-adapter#4 (koto9x/mediaremote-adapter feat/expose-adam-ids temporarily pinned in PR-A pbxproj until upstream merges). Stays in fork (not upstreamed): Lyrics9x provider, 3-tier romanization+translation, Plexamp player + smart routing, MediaRemote adapter callback fork-specific bits, Tailscale ATS exception, warmQueueWindow, sandbox fix for Plexamp, Kotopia Inc team/bundle ID. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds MusicKit-backed lyric fetching as the top-priority source when Music.app is the active player. For tracks in Apple Music's catalog, this returns Apple's official synced lyrics, which is dramatically more accurate than third-party sources for recent releases.
The motivating bug: aespa's LEMONADE album (released 2026-05) showed wrong-song or out-of-sync lyrics through the existing Spotify/LRClib/NetEase chain because none of those services had indexed the album yet — Apple Music had the correct TTML lyrics the entire time.
What's included
AppleMusicLyricProvider—MusicDataRequest-backed; parses Apple's TTML lyric envelope into[LyricLine]AppleMusicAuthManager— wrapsMusicAuthorization;@Observablestatus accessible app-wide via.sharedAppleMusicAuthView— first-run consent sheet (routed through aWindowscene, not.sheet— the latter is a silent no-op onMenuBarExtralabel views on macOS)TTMLParser— pure Swift TTML →[LyricLine]with XCTest fixtures (synced, plain/unsynced, malformed, Apple-realistic span-children)allNetworkLyricProvidersputs AM at index 0 when the AM player is active and a catalog (Adam) ID is known and auth is grantedSongObjectschema v2 — adds optionalappleMusicID: String?via lightweight Core Data migration; enables cross-player cache reuse when the same track was previously fetched under a different Spotify/Plexamp IDInfo.plist—NSAppleMusicUsageDescriptionstringWhat's NOT included (intentional, follow-up PRs)
Dependency
If that PR hasn't merged yet, the
pbxprojin this PR temporarily pinskoto9x/mediaremote-adapter@feat/expose-adam-ids(commit6825821) which already has the new fields. Once ejbills/mediaremote-adapter#4 merges, a one-line revert of therepositoryURLin thepbxprojswitches back to the canonical package.Setup (one-time)
Enable MusicKit on the App ID in Apple Developer portal → Identifiers → your bundle ID → check MusicKit → Save. No code change needed beyond what's in this PR.
Test plan
TTMLParserTests— synced timestamps, plain (zero timestamps), malformed throws, Apple span-children fixtureSongObjectMigrationTests(existing CoreData store migrates cleanly)