From 164a7b821fd313812eb35c9b31d6cc3447bd6cfe Mon Sep 17 00:00:00 2001 From: uh wot Date: Sat, 21 Sep 2024 00:17:27 +0200 Subject: [PATCH] add lyrics support to deezer, bump version --- package.json | 2 +- src/streamers/deezer/main.ts | 24 ++++++++-------- src/streamers/deezer/parse.ts | 53 +++++++++++++++++++++++++++++++++-- 3 files changed, 63 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 7c49273..c92ad25 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lucida", - "version": "2.0.0-46", + "version": "2.0.0-47", "description": "A modular music downloader tool", "main": "build/index.js", "type": "module", diff --git a/src/streamers/deezer/main.ts b/src/streamers/deezer/main.ts index a0a15df..3837f99 100644 --- a/src/streamers/deezer/main.ts +++ b/src/streamers/deezer/main.ts @@ -25,7 +25,8 @@ import { parseAlbum, parseArtist, parseTrack, - parsePlaylistMetadata + parsePlaylistMetadata, + DeezerLyrics } from './parse.js' import { Readable, Transform } from 'stream' import { Blowfish } from 'blowfish-cbc' @@ -51,6 +52,7 @@ interface APIMethod { } 'deezer.pageTrack': { DATA: DeezerTrack + LYRICS?: DeezerLyrics } 'deezer.pagePlaylist': { DATA: DeezerPlaylistMetadata @@ -318,7 +320,7 @@ export default class Deezer implements StreamerWithLogin { const data = { metadata: { ...parseAlbum(DATA), trackCount: SONGS.data.length }, - tracks: SONGS.data.map(parseTrack) + tracks: SONGS.data.map((t) => parseTrack(t)) } if (!data.metadata.trackCount) data.metadata.trackCount = data.tracks.length @@ -345,18 +347,16 @@ export default class Deezer implements StreamerWithLogin { return { type: 'playlist', metadata: parsePlaylistMetadata(playlistMetadata), - tracks: playlistTracks.map(parseTrack) + tracks: playlistTracks.map((t) => parseTrack(t)) } } // Track - async #getTrackData(id: number): Promise { - return ( - await this.#apiCall('deezer.pageTrack', { - sng_id: id - }) - ).DATA + async #getTrackPage(id: number): Promise { + return await this.#apiCall('deezer.pageTrack', { + sng_id: id + }) } async #getStream(track: DeezerTrack): Promise { @@ -531,12 +531,12 @@ export default class Deezer implements StreamerWithLogin { } } case 'track': { - const track = await this.#getTrackData(id) + const trackPage = await this.#getTrackPage(id) return { type: 'track', - metadata: parseTrack(track), + metadata: parseTrack(trackPage.DATA, trackPage.LYRICS), getStream: () => { - return this.#getStream(track) + return this.#getStream(trackPage.DATA) } } } diff --git a/src/streamers/deezer/parse.ts b/src/streamers/deezer/parse.ts index 1acfc2e..65af73c 100644 --- a/src/streamers/deezer/parse.ts +++ b/src/streamers/deezer/parse.ts @@ -1,4 +1,4 @@ -import { Album, Artist, CoverArtwork, Playlist, Track } from '../../types.js' +import { Album, Artist, CoverArtwork, LyricLine, Playlist, Track } from '../../types.js' import { SIZES } from './constants.js' export enum DeezerFormat { @@ -69,7 +69,7 @@ export function parseArtist( ) let parsedTracks - if (tracks) parsedTracks = tracks.map(parseTrack) + if (tracks) parsedTracks = tracks.map((t) => parseTrack(t)) let parsedAlbums if (albums) parsedAlbums = albums.map(parseAlbum) @@ -142,7 +142,20 @@ export interface DeezerTrack { FALLBACK?: DeezerTrack } -export function parseTrack(track: DeezerTrack): Track { +export interface DeezerLyrics { + LYRICS_ID: string + LYRICS_SYNC_JSON: DeezerSyncedLine[] + LYRICS_TEXT: string +} + +export interface DeezerSyncedLine { + lrc_timestamp?: string + milliseconds?: string + duration?: string + line: string +} + +export function parseTrack(track: DeezerTrack, lyricsData?: DeezerLyrics): Track { const addt: { regions?: string[] copyright?: string @@ -158,6 +171,39 @@ export function parseTrack(track: DeezerTrack): Track { if (track.SNG_CONTRIBUTORS?.composer) addt.composers = track.SNG_CONTRIBUTORS?.composer if (track.SNG_CONTRIBUTORS?.composer) addt.lyricists = track.SNG_CONTRIBUTORS?.lyricist + let lyrics + if (lyricsData) { + const lines: LyricLine[] = [] + + const syncedLyrics = lyricsData.LYRICS_SYNC_JSON + if (syncedLyrics) { + let lastLineEnd = 0 + for (const line of syncedLyrics) { + if (!line.line) { + const lastLine = lines[lines.length - 1] + if (lastLine) lastLine.endTimeMs = lastLineEnd + continue + } + + lines.push({ + text: line.line, + startTimeMs: parseInt(line.milliseconds!) + }) + lastLineEnd = parseInt(line.milliseconds!) + parseInt(line.duration!) + } + } else { + lines.push({ + text: lyricsData.LYRICS_TEXT + }) + } + + lyrics = { + id: lyricsData.LYRICS_ID, + source: 'deezer', + lines + } + } + return { title: track.VERSION ? `${track.SNG_TITLE} ${track.VERSION}` : track.SNG_TITLE, id: track.SNG_ID, @@ -174,6 +220,7 @@ export function parseTrack(track: DeezerTrack): Track { }), durationMs: parseInt(track.DURATION) * 1e3, coverArtwork: parseArtwork(track.ALB_PICTURE), + lyrics, ...addt } }