UNPKG

timestamped-lyrics

Version:

Simple Package to fetch and display timestamped lyrics for a given song.

163 lines (162 loc) 7.57 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.LyricalClient = void 0; const node_fetch_1 = __importDefault(require("node-fetch")); class LyricalClient { constructor(options) { this.options = options || {}; } /** * Search for lyrics of a song by its name and artist. Returns up to 20 results. * @param song The title of the song. * @param artist The name of the artist. * @param options Optional parameters for the query. * @returns Promise<LyricSearchResult[]> */ querySongLyrics(song, artist, options) { return __awaiter(this, void 0, void 0, function* () { const { syncedOnly = false } = options || {}; // Format the song and artist for the search query. const search = yield (0, node_fetch_1.default)(`https://lrclib.net/api/search?track_name=${song}&artist_name=${artist}`, { headers: { accept: "application/json", }, method: "GET", }); if (!search.ok) { throw new Error(`Failed to fetch lyrics for ${song} by ${artist}`); } const data = (yield search.json()); if (!data || !data.length) { throw new Error(`No lyrics found for ${song} by ${artist}`); } // Filter results based on syncedOnly flag if (syncedOnly) { return data.filter((item) => item.syncedLyrics && item.syncedLyrics.length > 0); } // Return all results if syncedOnly is false return data; }); } /** * Get closest matching lyrics for a song by title + artist, optionally filtered by duration. * @param song The title of the song. * @param artist The name of the artist. * @param duration Optional duration of the song in seconds. * If provided, the function will try to find the closest matching lyrics based on duration * If not provided, the first matching lyrics will be returned. * @returns Promise<LyricSearchResult> * */ getLyrics(song, artist, options) { return __awaiter(this, void 0, void 0, function* () { const { duration, syncedOnly = false } = options || {}; // Check if songduration is a valid number if (duration && typeof duration !== "number") { throw new Error("songduration must be a number"); } let data = yield this.querySongLyrics(song, artist); if (!data || !data.length) { throw new Error(`No lyrics found for ${song} by ${artist}`); } // find the closest match based on duration, or return the first match if (syncedOnly) { data = data.filter((item) => item.syncedLyrics && item.syncedLyrics.length > 0); } let lyric = undefined; if (duration) { const closestLyric = data.reduce((prev, curr) => { const prevDiff = Math.abs(prev.duration - duration); const currDiff = Math.abs(curr.duration - duration); return currDiff < prevDiff ? curr : prev; }); if (closestLyric) { lyric = closestLyric; } } else if (data.length > 0) { lyric = data[0]; // return the first matching lyric if no duration is provided } if (!lyric) { throw new Error(`No lyrics found for ${song} by ${artist}`); } return lyric; }); } /** * Get synced lyric lines for a song by title + artist. * This function retrieves the synced lyrics for a song and returns them as an array of LyricLine objects. * Each LyricLine object contains the start time, end time, and text of the lyric line. * If no synced lyrics are available, an error is thrown. * @param song The title of the song. * @param artist The name of the artist. * @param options Optional parameters for the lyrics retrieval. * @returns */ getLyricLines(song, artist, options) { return __awaiter(this, void 0, void 0, function* () { const { syncedOnly = true, duration } = options || {}; // Get the lyrics for the song const lyrics = yield this.getLyrics(song, artist, { duration, syncedOnly: true, // always get synced lyrics }); // Parse the synced lyrics from the LyricSearchResult object return LyricalClient.parseSyncedLyrics(lyrics); }); } /** * Parse synced lyrics from a LyricSearchResult object. * @param lyrics The LyricSearchResult object containing synced lyrics. * @returns LyricLine[] An array of LyricLine objects representing the parsed synced lyrics. * @throws Error if no synced lyrics are available or if the format is incorrect. * @example * const lyrics = await client.getLyrics("Song Title", "Artist Name"); * const parsedLyrics = LyricalClient.parseSyncedLyrics(lyrics); * */ static parseSyncedLyrics(lyrics) { const lyricalLines = []; if (!lyrics.syncedLyrics || !lyrics.syncedLyrics.length) { throw new Error("No synced lyrics available for this song"); } const lyricalArray = lyrics.syncedLyrics.split("\n"); let startingLines = []; for (const line of lyricalArray) { if (!line.trim()) continue; // skip empty lines const match = line.match(/^\[(\d{2}):(\d{2}\.\d{2})\](.*)$/); if (!match) continue; // skip lines that don't match the format const start = Number(match[1]) * 60 + parseFloat(match[2]); let text = match[3].trim(); if (!text) text = "🎵"; // ensure text is not empty if (text.includes("\n")) { text = text.replace(/\n/g, " "); // replace newlines with spaces } startingLines.push({ line: text, start }); } for (let i = 0; i < startingLines.length; i++) { const currentLine = startingLines[i]; const nextLine = startingLines[i + 1]; const start = currentLine.start; const end = nextLine ? nextLine.start : lyrics.duration; // use duration if no next line lyricalLines.push({ start, end, text: currentLine.line }); } return lyricalLines; } } exports.LyricalClient = LyricalClient;