### Create and Use Individual Lyric Line Source: https://context7.com/ddddxxx/lyricskit/llms.txt Shows how to create a LyricsLine object, format its time tag, and navigate to adjacent lines if the line is part of a Lyrics document. Lines are considered equal if their content, position, and attachments match. ```swift import LyricsCore let line = LyricsLine(content: "銀の龍の背に乗って", position: 42.150) // Formatted [mm:ss.xxx] time tag string print(line.timeTag) // Navigate to previous/next line (requires line to be part of a Lyrics object) if let next = line.nextLine() { print(next.content) } if let prev = line.previousLine() { print(prev.content) } // Equatable: two lines are equal if position, content, and attachments match let copy = LyricsLine(content: "銀の龍の背に乗って", position: 42.150) print(line == copy) // Description produces LRCX-formatted output including all attachment lines print(line.description) ``` -------------------------------- ### Parse and Use Lyrics Document Source: https://context7.com/ddddxxx/lyricskit/llms.txt Demonstrates parsing an LRC/LRCX string into a Lyrics object, accessing its properties, performing playback position lookups, applying timing offsets, and serializing back to string formats. Ensure the input string is valid LRC/LRCX content. ```swift import LyricsCore // Parse from an LRC/LRCX string let lrcString = """ [ti:Tranquilize] [ar:The Killers] [length:225.2] [00:12.340]Some lyrics line [00:12.340][tr:zh-Hans]中文翻译 [00:18.500]Another lyrics line """ guard let lrc = Lyrics(lrcString) else { fatalError("Invalid LRC content") } // Basic properties print(lrc.idTags[.title]!) print(lrc.idTags[.artist]!) print(lrc.length!) print(lrc.count) // Playback position lookup let idx = lrc.lineIndex(at: 15.0) if let idx = idx { print(lrc[idx].content) print(lrc[idx].attachments.translation(languageCodeCandidate: ["zh-Hans"])!) } // Apply a global timing offset (in seconds) lrc.timeDelay = 0.5 print(lrc.offset) // Serialize back to LRCX string let serialized = lrc.description // Re-serialize in legacy LRC format (translations inlined as 【...】) let legacy = lrc.legacyDescription ``` -------------------------------- ### Search Lyrics from Internet in Swift Source: https://github.com/ddddxxx/lyricskit/blob/master/README.md Demonstrates how to create a search request for lyrics using LyricsKit and a chosen provider. Ensure LyricsService is imported. ```swift import LyricsService // create a search request let song = "Tranquilize" let artist = "The Killers" let duration = 225.2 let searchReq = LyricsSearchRequest( searchTerm: .info(title: song, artist: artist), duration: duration ) // choose a lyrics service provider let provider = LyricsProviders.Kugou() // or search from multiple sources let provider = LyricsProviders.Group(service: [.kugou, .netease, .qq]) // search provider.lyricsPublisher(request: searchReq).sink { lyrics in print(lyrics) } ``` -------------------------------- ### Render Service Icons with LyricsSourceIconDrawing Source: https://context7.com/ddddxxx/lyricskit/llms.txt Renders branded vector icons for music services using native Bezier paths. Available on macOS and iOS/tvOS. Requires CoreGraphics import. ```swift #if canImport(CoreGraphics) import LyricsService // In a custom NSView/UIView draw(_:) override or image context: // Draw a 48×48 NetEase Music icon let rect = CGRect(x: 0, y: 0, width: 48, height: 48) LyricsSourceIconDrawing.drawNetEaseMusic(frame: rect) // Draw a QQ Music icon LyricsSourceIconDrawing.drawQQMusic(frame: rect) // Draw a Kugou Music icon LyricsSourceIconDrawing.drawKugou(frame: rect) // Draw a Gecimi icon LyricsSourceIconDrawing.drawGecimi(frame: rect) // Access brand colors directly let neRed = LyricsSourceIconDrawing.netEaseRed // #DC001A let qqYellow = LyricsSourceIconDrawing.qqMusicYellow // #F0C32A let kgBlue = LyricsSourceIconDrawing.kugouBlue // #1B7BCC // Render to NSImage (macOS example) let image = NSImage(size: CGSize(width: 48, height: 48)) image.lockFocus() LyricsSourceIconDrawing.drawNetEaseMusic() image.unlockFocus() #endif ``` -------------------------------- ### Enumerate Available Lyrics Service Providers Source: https://context7.com/ddddxxx/lyricskit/llms.txt LyricsProviders.Service enumerates all available lyrics sources and provides a factory method to create individual provider instances. You can also filter services to create custom groups. ```swift import LyricsService // All available services print(LyricsProviders.Service.allCases.map(\.rawValue)) // ["163", "QQMusic", "Kugou", "Gecimi", "Syair"] // Create a provider from a service enum value let provider = LyricsProviders.Service.netease.create() // Returns a LyricsProviders.NetEase() instance // Build a Group from a filtered set of services let chineseServicesGroup = LyricsProviders.Group( service: LyricsProviders.Service.allCases.filter { $0 != .syair } ) ``` -------------------------------- ### Search Across Multiple Services with LyricsProviders.Group Source: https://context7.com/ddddxxx/lyricskit/llms.txt Use LyricsProviders.Group to search across multiple services simultaneously. You can search all available services or a specific subset. ```swift import Combine import LyricsService // Search all available services (default) let allProviders = LyricsProviders.Group() // Search only a subset of services let subset = LyricsProviders.Group(service: [.kugou, .netease, .qq]) let request = LyricsSearchRequest( searchTerm: .info(title: "Uprising", artist: "Muse"), duration: 305.0, limit: 10 ) var cancellables = Set() allProviders.lyricsPublisher(request: request) .sink { lyrics in print("[\(lyrics.metadata.service!.rawValue)] \(lyrics.idTags[.title] ?? "?") — quality: \(String(format: "%.2f", lyrics.quality))") } .store(in: &cancellables) // Example output: // [Kugou] Uprising — quality: 1.25 // [163] Uprising — quality: 1.30 // [QQMusic] Uprising — quality: 1.10 ``` -------------------------------- ### Fetch Lyrics from QQ Music Source: https://context7.com/ddddxxx/lyricskit/llms.txt Use LyricsProviders.QQMusic to search and fetch lyrics from QQ Music. It automatically merges translation lyrics when available. ```swift import Combine import LyricsService let provider = LyricsProviders.QQMusic() let request = LyricsSearchRequest( searchTerm: .info(title: "晴天", artist: "周杰倫"), duration: 269.0 ) var cancellables = Set() provider.lyricsPublisher(request: request) .first() .sink { lyrics in print(lyrics.idTags[.title]!) print(lyrics.idTags[.artist]!) print(lyrics.metadata.hasTranslation) for line in lyrics { if let trans = line.attachments.translation() { print("\(line.content) — \(trans)") } } } .store(in: &cancellables) ``` -------------------------------- ### LyricsProviders.Service Source: https://context7.com/ddddxxx/lyricskit/llms.txt Enumerates all available lyrics sources and provides a factory method to create an individual provider instance. ```APIDOC ## LyricsProviders.Service ### Description Enumerates all available (non-discontinued) lyrics sources and provides a factory method to create an individual provider instance. ### Usage ```swift import LyricsService // All available services print(LyricsProviders.Service.allCases.map(\.rawValue)) // ["163", "QQMusic", "Kugou", "Gecimi", "Syair"] // Create a provider from a service enum value let provider = LyricsProviders.Service.netease.create() // Returns a LyricsProviders.NetEase() instance // Build a Group from a filtered set of services let chineseServicesGroup = LyricsProviders.Group( service: LyricsProviders.Service.allCases.filter { $0 != .syair } ) ``` ### Methods - `allCases`: Returns an array of all available `LyricsProviders.Service` cases. - `create()`: Creates an instance of the corresponding lyrics provider. ``` -------------------------------- ### Fetch Lyrics from Kugou Music Source: https://context7.com/ddddxxx/lyricskit/llms.txt Use LyricsProviders.Kugou to search and fetch lyrics from Kugou Music using the encrypted KRC format, which includes millisecond-precision inline time tags. ```swift import Combine import LyricsService let provider = LyricsProviders.Kugou() let request = LyricsSearchRequest( searchTerm: .info(title: "Tranquilize", artist: "The Killers"), duration: 225.2 ) var cancellables = Set() provider.lyricsPublisher(request: request) .prefix(3) .sink { lyrics in print("Duration: \(lyrics.length ?? 0)") print("Has timetag: \(lyrics.metadata.attachmentTags.contains(.timetag))") print("Token: \(lyrics.metadata.serviceToken!)") } .store(in: &cancellables) ``` -------------------------------- ### Construct Lyrics Search Requests Source: https://context7.com/ddddxxx/lyricskit/llms.txt Create LyricsSearchRequest objects to specify search parameters, including search terms (title+artist or keyword), duration, and result limit. User-defined data can be included via userInfo. ```swift import LyricsService // Search by title + artist (recommended for best matching) let req1 = LyricsSearchRequest( searchTerm: .info(title: "Bohemian Rhapsody", artist: "Queen"), duration: 354.0, limit: 6 ) // Search by keyword string let req2 = LyricsSearchRequest( searchTerm: .keyword("Queen Bohemian Rhapsody"), duration: 354.0, limit: 3, userInfo: ["source": "user-input"] ) // The description of a .info term is "title artist" print(req1.searchTerm.description) print(req2.searchTerm.description) ``` -------------------------------- ### LyricsProviders.QQMusic Source: https://context7.com/ddddxxx/lyricskit/llms.txt Searches and fetches lyrics from QQ Music. Automatically merges translation lyrics when available. ```APIDOC ## LyricsProviders.QQMusic ### Description Searches and fetches lyrics from QQ Music. Automatically merges translation lyrics when available. ### Usage ```swift import Combine import LyricsService let provider = LyricsProviders.QQMusic() let request = LyricsSearchRequest( searchTerm: .info(title: "晴天", artist: "周杰倫"), duration: 269.0 ) var cancellables = Set() provider.lyricsPublisher(request: request) .first() .sink { lyrics in print(lyrics.idTags[.title]!) print(lyrics.idTags[.artist]!) print(lyrics.metadata.hasTranslation) for line in lyrics { if let trans = line.attachments.translation() { print("\(line.content) — \(trans)") } } } .store(in: &cancellables) ``` ### Method `lyricsPublisher(request: LyricsSearchRequest) -> AnyPublisher` ``` -------------------------------- ### Manage Per-Line Metadata with Attachments Source: https://context7.com/ddddxxx/lyricskit/llms.txt Use LyricsLine.Attachments to store and retrieve per-line metadata like translations, time tags, furigana, and romaji. Supports typed accessors and arbitrary string tags. ```swift import LyricsCore var attachments = LyricsLine.Attachments() // Set a Chinese translation attachments[.translation(languageCode: "zh-Hans")] = "中文翻译" // Set a generic (language-agnostic) translation attachments[.translation()] = "English translation" // Read translation with language preference fallback let text = attachments.translation(languageCodeCandidate: ["zh-Hans", nil]) print(text!) // Inline word-level time tag (for karaoke highlighting) let tt = LyricsLine.Attachments.InlineTimeTag(tags: [ .init(index: 0, time: 0.000), .init(index: 2, time: 0.210), .init(index: 4, time: 0.530) ], duration: 1.200) attachments.timetag = tt print(tt.description) // Furigana / romaji range annotations let furigana = LyricsLine.Attachments.RangeAttribute(attributes: [ .init(range: 0..<1, content: "ぎん"), .init(range: 1..<2, content: "りゅう") ]) attachments.furigana = furigana print(furigana.description) // Check which attachment types are present print(attachments.content.keys.map(\.rawValue)) ``` -------------------------------- ### LyricsSourceIconDrawing Source: https://context7.com/ddddxxx/lyricskit/llms.txt Provides static methods for rendering branded vector icons of music services on Apple platforms. These icons can be drawn into the current graphics context. ```APIDOC ## LyricsSourceIconDrawing — service icon rendering (Apple platforms) ### Description `LyricsSourceIconDrawing` provides static `draw*` methods that render branded vector icons for each music service using native Bezier paths into the current graphics context. Available on macOS (Cocoa) and iOS/tvOS (UIKit). ### Usage Example ```swift #if canImport(CoreGraphics) import LyricsService // In a custom NSView/UIView draw(_:) override or image context: // Draw a 48×48 NetEase Music icon let rect = CGRect(x: 0, y: 0, width: 48, height: 48) LyricsSourceIconDrawing.drawNetEaseMusic(frame: rect) // Draw a QQ Music icon LyricsSourceIconDrawing.drawQQMusic(frame: rect) // Draw a Kugou Music icon LyricsSourceIconDrawing.drawKugou(frame: rect) // Draw a Gecimi icon LyricsSourceIconDrawing.drawGecimi(frame: rect) // Access brand colors directly let neRed = LyricsSourceIconDrawing.netEaseRed // #DC001A let qqYellow = LyricsSourceIconDrawing.qqMusicYellow // #F0C32A let kgBlue = LyricsSourceIconDrawing.kugouBlue // #1B7BCC // Render to NSImage (macOS example) let image = NSImage(size: CGSize(width: 48, height: 48)) image.lockFocus() LyricsSourceIconDrawing.drawNetEaseMusic() image.unlockFocus() #endif ``` ``` -------------------------------- ### LyricsProviders.Kugou Source: https://context7.com/ddddxxx/lyricskit/llms.txt Searches and fetches from Kugou Music using the encrypted KRC format, which includes millisecond-precision inline time tags for karaoke-style display. ```APIDOC ## LyricsProviders.Kugou ### Description Searches and fetches from Kugou Music using the encrypted KRC format, which includes millisecond-precision inline time tags for karaoke-style display. ### Usage ```swift import Combine import LyricsService let provider = LyricsProviders.Kugou() let request = LyricsSearchRequest( searchTerm: .info(title: "Tranquilize", artist: "The Killers"), duration: 225.2 ) var cancellables = Set() provider.lyricsPublisher(request: request) .prefix(3) .sink { lyrics in print("Duration: \(lyrics.length ?? 0)") print("Has timetag: \(lyrics.metadata.attachmentTags.contains(.timetag))") print("Token: \(lyrics.metadata.serviceToken!)") } .store(in: &cancellables) ``` ### Method `lyricsPublisher(request: LyricsSearchRequest) -> AnyPublisher` ``` -------------------------------- ### LyricsProviders.NetEase Source: https://context7.com/ddddxxx/lyricskit/llms.txt Searches and fetches lyrics from NetEase Music (music.163.com). Supports both standard LRC and the proprietary KLyric format, including inline word-level time tags and translations. ```APIDOC ## LyricsProviders.NetEase ### Description Searches and fetches lyrics from NetEase Music (music.163.com). Supports both standard LRC and the proprietary KLyric format (with inline word-level time tags and translations). ### Usage ```swift import Combine import LyricsService let provider = LyricsProviders.NetEase() let request = LyricsSearchRequest( searchTerm: .info(title: "One Last You", artist: "光田康典"), duration: 0 // 0 means duration matching is skipped ) var cancellables = Set() provider.lyricsPublisher(request: request) .first() .sink { lyrics in print(lyrics.idTags[.title]!) print(lyrics.idTags[.artist]!) print(lyrics.metadata.artworkURL!) print(lyrics.metadata.serviceToken!) print(lyrics.count) if lyrics.metadata.attachmentTags.contains(.timetag) { print("Has inline karaoke time tags") } } .store(in: &cancellables) ``` ### Method `lyricsPublisher(request: LyricsSearchRequest) -> AnyPublisher` ``` -------------------------------- ### Use Standard ID Tags with Lyrics.IDTagKey Source: https://context7.com/ddddxxx/lyricskit/llms.txt Employ Lyrics.IDTagKey for indexing Lyrics.idTags to store standard LRC metadata fields like title, artist, and album. Custom tags are also supported. ```swift import LyricsCore var tags: [Lyrics.IDTagKey: String] = [:] tags[.title] = "Uprising" tags[.artist] = "Muse" tags[.album] = "The Resistance" tags[.author] = "Matt Bellamy" tags[.lrcBy] = "contributor" tags[.offset] = "200" // milliseconds as string tags[.length] = "305" // seconds as string // Custom / vendor tags work too let customKey = Lyrics.IDTagKey("my_custom_tag") tags[customKey] = "custom value" // Access via the Lyrics convenience properties var lrc = Lyrics(lines: [], idTags: tags) print(lrc.offset) print(lrc.timeDelay) print(lrc.length!) ``` -------------------------------- ### Generate Furigana for CJK Characters in Lyrics Source: https://context7.com/ddddxxx/lyricskit/llms.txt Automatically annotates CJK characters in lyrics with phonetic hiragana readings using CFStringTokenizer. Requires Apple platforms and LyricsCore import. ```swift #if canImport(Darwin) import LyricsCore // Parse lyrics containing kanji let lrcString = """ [00:10.000]銀の龍の背に乗って [00:20.000]明日の空へ """ let lrc = Lyrics(lrcString)! // Generate furigana for all lines lrc.generateFurigana() // Each line now has furigana attachments for line in lrc { if let furigana = line.attachments.furigana { for attr in furigana.attributes { let original = line.content let charSlice = original[ original.index(original.startIndex, offsetBy: attr.range.lowerBound) ..< original.index(original.startIndex, offsetBy: attr.range.upperBound) ] print("\(charSlice) → \(attr.content)") // e.g. "銀" → "ぎん", "龍" → "りゅう" } } } #endif ``` -------------------------------- ### Fetch Lyrics from NetEase Music Source: https://context7.com/ddddxxx/lyricskit/llms.txt Use LyricsProviders.NetEase to search and fetch lyrics from NetEase Music. This provider supports standard LRC and proprietary KLyric formats. ```swift import Combine import LyricsService let provider = LyricsProviders.NetEase() let request = LyricsSearchRequest( searchTerm: .info(title: "One Last You", artist: "光田康典"), duration: 0 // 0 means duration matching is skipped ) var cancellables = Set() provider.lyricsPublisher(request: request) .first() .sink { lyrics in print(lyrics.idTags[.title]!) print(lyrics.idTags[.artist]!) print(lyrics.metadata.artworkURL!) print(lyrics.metadata.serviceToken!) print(lyrics.count) if lyrics.metadata.attachmentTags.contains(.timetag) { print("Has inline karaoke time tags") } } .store(in: &cancellables) ``` -------------------------------- ### Search and Score Lyrics with LyricsProviders.Group Source: https://context7.com/ddddxxx/lyricskit/llms.txt Fetches lyrics from multiple providers and sorts them by quality score. Ensure LyricsService is imported. ```swift import Combine import LyricsService let group = LyricsProviders.Group() let request = LyricsSearchRequest( searchTerm: .info(title: "Uprising", artist: "Muse"), duration: 305.0, limit: 20 ) var results: [Lyrics] = [] var cancellables = Set() group.lyricsPublisher(request: request) .collect() .sink { results = lyrics.sorted { $0.quality > $1.quality } if let best = results.first { print("Best match: \(best.idTags[.title]!)\ from \(best.metadata.service!.rawValue)") print("Quality: \(best.quality)") print("Is exact match: \(best.isMatched())") } } .store(in: &cancellables) ``` -------------------------------- ### Lyrics.generateFurigana() Source: https://context7.com/ddddxxx/lyricskit/llms.txt Automatically generates phonetic hiragana readings for CJK characters in lyrics lines on Apple platforms using CFStringTokenizer. These readings are stored as RangeAttribute furigana attachments. ```APIDOC ## Lyrics.generateFurigana() — automatic furigana generation (Apple platforms) ### Description On Apple platforms, `generateFurigana()` uses `CFStringTokenizer` to automatically annotate CJK characters in every line with phonetic hiragana readings, storing them as `RangeAttribute` furigana attachments. ### Usage Example ```swift #if canImport(Darwin) import LyricsCore // Parse lyrics containing kanji let lrcString = """ [00:10.000]銀の龍の背に乗って [00:20.000]明日の空へ """ let lrc = Lyrics(lrcString)! // Generate furigana for all lines lrc.generateFurigana() // Each line now has furigana attachments for line in lrc { if let furigana = line.attachments.furigana { for attr in furigana.attributes { let original = line.content let charSlice = original[ original.index(original.startIndex, offsetBy: attr.range.lowerBound) ..< original.index(original.startIndex, offsetBy: attr.range.upperBound) ] print("\(charSlice) → \(attr.content)") // e.g. "銀" → "ぎん", "龍" → "りゅう" } } } #endif ``` ``` -------------------------------- ### Access Lyrics Service and Search Metadata Source: https://context7.com/ddddxxx/lyricskit/llms.txt Inspect Lyrics.Metadata after fetching Lyrics objects to access service information, remote URLs, quality scores, and search request details. Metadata helps in evaluating the quality and origin of the lyrics. ```swift import LyricsCore import LyricsService // After receiving a Lyrics object from a provider, inspect metadata: let lrc: Lyrics = /* received from provider */ print(lrc.metadata.service!) print(lrc.metadata.remoteURL!) print(lrc.metadata.artworkURL!) print(lrc.metadata.serviceToken!) print(lrc.metadata.searchRequest!) print(lrc.metadata.attachmentTags) print(lrc.metadata.hasTranslation) // Quality score (0.0 – ~1.4, higher is better) // Boosted by: matched title (+factor), matched artist (+factor), // has translation (+0.1), has inline time tags (+0.1) print(lrc.quality) // Check if lyrics title/artist match the search request print(lrc.isMatched()) ``` -------------------------------- ### LyricsLine.Attachments Source: https://context7.com/ddddxxx/lyricskit/llms.txt The Attachments type stores per-line metadata such as translations, time tags, furigana, and romaji. It allows for typed accessors for common metadata and supports arbitrary tags via string subscript. ```APIDOC ## LyricsLine.Attachments — per-line metadata The `Attachments` type stores a dictionary of `Tag → LyricsLineAttachment`. Built-in typed accessors exist for translations (`.translation(languageCode:)`), inline word time tags (`.timetag`), furigana (`.furigana`), and romaji (`.romaji`). Arbitrary tags can be read/written via string subscript. ```swift import LyricsCore var attachments = LyricsLine.Attachments() // Set a Chinese translation attachments[.translation(languageCode: "zh-Hans")] = "中文翻译" // Set a generic (language-agnostic) translation attachments[.translation()] = "English translation" // Read translation with language preference fallback let text = attachments.translation(languageCodeCandidate: ["zh-Hans", nil]) print(text!) // Inline word-level time tag (for karaoke highlighting) let tt = LyricsLine.Attachments.InlineTimeTag(tags: [ .init(index: 0, time: 0.000), .init(index: 2, time: 0.210), .init(index: 4, time: 0.530) ], duration: 1.200) attachments.timetag = tt print(tt.description) // Furigana / romaji range annotations let furigana = LyricsLine.Attachments.RangeAttribute(attributes: [ .init(range: 0..<1, content: "ぎん"), .init(range: 1..<2, content: "りゅう") ]) attachments.furigana = furigana print(furigana.description) // Check which attachment types are present print(attachments.content.keys.map(\.rawValue)) ``` ``` -------------------------------- ### LyricsProviders.Group Source: https://context7.com/ddddxxx/lyricskit/llms.txt Searches across multiple lyrics services simultaneously by fanning out a single LyricsSearchRequest. This is useful when you need the broadest possible result set without specifying a particular service. ```APIDOC ## LyricsProviders.Group ### Description Fans out a single `LyricsSearchRequest` to multiple providers simultaneously and merges their outputs into one `AnyPublisher`. Use it when you want the broadest result set without caring which service responds. ### Usage ```swift import Combine import LyricsService // Search all available services (default) let allProviders = LyricsProviders.Group() // Search only a subset of services let subset = LyricsProviders.Group(service: [.kugou, .netease, .qq]) let request = LyricsSearchRequest( searchTerm: .info(title: "Uprising", artist: "Muse"), duration: 305.0, limit: 10 ) var cancellables = Set() allProviders.lyricsPublisher(request: request) .sink { lyrics in print("[\(lyrics.metadata.service!.rawValue)] \(lyrics.idTags[.title] ?? "?") — quality: \(String(format: "%.2f", lyrics.quality))") } .store(in: &cancellables) // Example output: // [Kugou] Uprising — quality: 1.25 // [163] Uprising — quality: 1.30 // [QQMusic] Uprising — quality: 1.10 ``` ### Parameters - `service`: An optional array of `LyricsProviders.Service` to specify which providers to include in the group. If nil, all available services are used. ``` -------------------------------- ### LRCX File Specification Source: https://github.com/ddddxxx/lyricskit/blob/master/README.md Defines the grammar and structure for LRCX files, including lines, tags, time tags, and attachments. ```ebnf ::= (NEWLINE )* ::= | | | "" ::= ::= "[" "]" ::= | ":" ::= [0-9a-zA-Z_-]+ ::= ::=