module SingSources
Public Instance Methods
Source
# File lib/sing_sources.rb, line 178 def build_elyrics_url(artist, title) artist_slug = slugify(artist) title_slug = slugify(title) "#{ELYRICS_BASE_URL}#{artist_slug[0]}/#{artist_slug}-lyrics/#{title_slug}-lyrics.html" end
Source
# File lib/sing_sources.rb, line 162 def build_lrclib_get_url(match) params = { track_name: match[:title], artist_name: match[:artist], album_name: match[:album], duration: match[:duration] }.compact query = params.map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") "#{LRCLIB_GET_URL}?#{query}" end
Source
# File lib/sing_sources.rb, line 173 def build_lrclib_search_url(artist, title) query = "track_name=#{CGI.escape(title.to_s.strip)}&artist_name=#{CGI.escape(artist.to_s.strip)}" "#{LRCLIB_SEARCH_URL}?#{query}" end
Source
# File lib/sing_sources.rb, line 158 def build_lyrics_url(artist, title) "#{LYRICS_BASE_URL}#{CGI.escape(artist.to_s.strip)}/#{CGI.escape(title.to_s.strip)}" end
Source
# File lib/sing_sources.rb, line 154 def build_suggest_url(query) "#{SUGGEST_BASE_URL}#{CGI.escape(query.to_s.strip)}" end
Source
# File lib/sing_sources.rb, line 209 def fetch_elyrics_lyrics(match, fetch:) html = fetch.call(build_elyrics_url(match[:artist], match[:title])) parse_elyrics_html(html) end
Source
# File lib/sing_sources.rb, line 198 def fetch_lrclib_get(match, fetch:) return nil unless match[:album] && match[:duration] result = parse_lrclib_response( fetch.call(build_lrclib_get_url(match)), artist: match[:artist], title: match[:title] ) result&.dig(:lyrics) end
Source
# File lib/sing_sources.rb, line 189 def fetch_lrclib_search(match, fetch:) result = parse_lrclib_response( fetch.call(build_lrclib_search_url(match[:artist], match[:title])), artist: match[:artist], title: match[:title] ) result&.dig(:lyrics) end
Source
# File lib/sing_sources.rb, line 214 def fetch_lyrics_for_match(match, fetch:) title_variants(match[:title]).each do |title| candidate = match.merge(title: title) lyrics = fetch_lrclib_search(candidate, fetch: fetch) || fetch_lrclib_get(candidate, fetch: fetch) || fetch_lyrics_ovh(candidate, fetch: fetch) || fetch_elyrics_lyrics(candidate, fetch: fetch) return lyrics if lyrics end nil end
Source
# File lib/sing_sources.rb, line 184 def fetch_lyrics_ovh(match, fetch:) json = fetch.call(build_lyrics_url(match[:artist], match[:title])) parse_lyrics_response(json) end
Source
# File lib/sing_sources.rb, line 120 def normalize_match_token(text) TextUtil.squish(text).downcase.gsub(/[^a-z0-9]+/, "") end
Source
# File lib/sing_sources.rb, line 140 def parse_elyrics_html(html) node = Nokogiri::HTML(html.to_s).at("#inlyr") return nil unless node text = node.inner_html.gsub(%r{<br\s*/?>}i, "\n") body = Nokogiri::HTML.fragment(text).text body = SingFormat.normalize_lyrics(body) body.empty? ? nil : body end
Source
# File lib/sing_sources.rb, line 65 def parse_lrclib_response(json, artist: nil, title: nil) data = json.is_a?(String) ? JSON.parse(json) : json return nil if data.is_a?(Hash) && (data["name"] == "TrackNotFound" || data["statusCode"] == 404) return nil if data.is_a?(Array) && data.empty? item = if data.is_a?(Array) pick_lrclib_match(data, artist: artist, title: title) else data end return nil unless item lyrics = item["plainLyrics"].to_s if lyrics.strip.empty? && !item["syncedLyrics"].to_s.strip.empty? lyrics = strip_synced_lyrics(item["syncedLyrics"]) end return nil if lyrics.strip.empty? { artist: item["artistName"].to_s.strip, title: item["trackName"].to_s.strip, lyrics: SingFormat.normalize_lyrics(lyrics) } rescue JSON::ParserError nil end
Source
# File lib/sing_sources.rb, line 55 def parse_lyrics_response(json) data = json.is_a?(String) ? JSON.parse(json) : json return nil if data["error"] return nil if data["lyrics"].to_s.strip.empty? SingFormat.normalize_lyrics(data["lyrics"]) rescue JSON::ParserError nil end
Source
# File lib/sing_sources.rb, line 22 def parse_query(query) text = query.to_s.strip return nil if text.empty? return nil unless text.match?(QUERY_SPLIT_PATTERN) artist, title = text.split(QUERY_SPLIT_PATTERN, 2) artist = artist.to_s.strip title = title.to_s.strip return nil if artist.empty? || title.empty? { artist: artist, title: title } end
Source
# File lib/sing_sources.rb, line 35 def parse_suggest_response(json) data = json.is_a?(String) ? JSON.parse(json) : json items = data["data"] return nil if items.nil? || items.empty? item = items.find { |entry| entry["readable"] != false } || items.first artist = item.dig("artist", "name").to_s.strip title = item["title_short"].to_s.strip title = item["title"].to_s.strip if title.empty? return nil if artist.empty? || title.empty? match = { artist: artist, title: title } album = item.dig("album", "title").to_s.strip match[:album] = album unless album.empty? match[:duration] = item["duration"] if item["duration"] match rescue JSON::ParserError nil end
Source
# File lib/sing_sources.rb, line 92 def pick_lrclib_match(results, artist:, title:) return nil if results.nil? || results.empty? artist_needle = normalize_match_token(artist) title_needle = normalize_match_token(title) return results.first if artist_needle.empty? && title_needle.empty? scored = results.map do |entry| entry_artist = normalize_match_token(entry["artistName"]) entry_title = normalize_match_token(entry["trackName"]) score = 0 score += 20 if entry_artist == artist_needle score += 10 if !artist_needle.empty? && (entry_artist.include?(artist_needle) || artist_needle.include?(entry_artist)) score += 20 if entry_title == title_needle score += 12 if !title_needle.empty? && entry_title.start_with?(title_needle) score += 8 if !title_needle.empty? && title_needle.start_with?(entry_title) score += 4 unless entry["plainLyrics"].to_s.strip.empty? score -= 6 if entry_title.include?("explicit") { entry: entry, score: score } end best = scored.max_by { |row| row[:score] } return nil unless best return nil if best[:score].negative? best[:entry] end
Source
# File lib/sing_sources.rb, line 150 def slugify(text) text.to_s.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-+|-+\z/, "") end
Source
# File lib/sing_sources.rb, line 136 def strip_synced_lyrics(text) text.to_s.gsub(/\[\d{1,2}:\d{2}(?:\.\d{1,3})?\]\s*/, "") end
Source
# File lib/sing_sources.rb, line 124 def title_variants(title) base = TextUtil.squish(title) return [] if base.empty? variants = [base] stripped_paren = base.sub(/\s*[\(\[].+[\)\]]\z/, "").strip variants << stripped_paren unless stripped_paren.empty? || stripped_paren == base short_title = base.split(/,\s*/, 2).first.to_s.strip variants << short_title unless short_title.empty? || short_title == base variants.uniq end