mirror of
https://codeberg.org/lucida/lucida.git
synced 2026-11-12 22:57:22 -05:00
[tidal] add playlists support
This commit is contained in:
@@ -16,11 +16,13 @@ import {
|
||||
Contributor,
|
||||
RawAlbum,
|
||||
RawArtist,
|
||||
RawPlaylist,
|
||||
RawTrack,
|
||||
addCredits,
|
||||
parseAlbum,
|
||||
parseArtist,
|
||||
parseMpd,
|
||||
parsePlaylist,
|
||||
parseTrack
|
||||
} from './parse.js'
|
||||
import Stream, { Readable } from 'stream'
|
||||
@@ -306,6 +308,20 @@ export default class Tidal implements Streamer {
|
||||
)
|
||||
return contributorResponse.items.map((item) => parseTrack(addCredits(item.item, item.credits)))
|
||||
}
|
||||
async #getPlaylist(playlistId: number | string) {
|
||||
const playlistResponse = await this.#get(`playlists/${playlistId}`)
|
||||
return parsePlaylist(<RawPlaylist>playlistResponse)
|
||||
}
|
||||
async #getPlaylistTracks(playlistId: number | string) {
|
||||
const tracksResponse = <{ items: RawTrack[] }>await this.#get(
|
||||
`playlists/${playlistId}/tracks`,
|
||||
{
|
||||
offset: 0,
|
||||
limit: 10000
|
||||
}
|
||||
)
|
||||
return tracksResponse.items.map((item) => parseTrack(item))
|
||||
}
|
||||
async #getArtist(artistId: number | string) {
|
||||
const [artistResponse, albumsResponse, tracksResponse] = await Promise.all([
|
||||
<Promise<RawArtist>>this.#get(`artists/${artistId}`),
|
||||
@@ -458,13 +474,18 @@ export default class Tidal implements Streamer {
|
||||
return ['-hide_banner', '-loglevel', 'error', '-i', '-', '-c:a', 'copy', '-f', 'flac', '-']
|
||||
}
|
||||
}
|
||||
#getUrlParts(url: string): ['artist' | 'album' | 'track', string] {
|
||||
#getUrlParts(url: string): ['artist' | 'album' | 'track' | 'playlist', string] {
|
||||
const urlParts = url
|
||||
.match(/^https?:\/\/(?:www\.|listen\.)?tidal\.com\/(?:browse\/)?(.*?)\/(.*?)\/?$/)
|
||||
?.slice(1, 3)
|
||||
if (!urlParts) throw new Error('URL not supported')
|
||||
urlParts[1] = urlParts[1].replace(/\?.*?$/, '')
|
||||
if (urlParts[0] != 'artist' && urlParts[0] != 'album' && urlParts[0] != 'track') {
|
||||
if (
|
||||
urlParts[0] != 'artist' &&
|
||||
urlParts[0] != 'album' &&
|
||||
urlParts[0] != 'track' &&
|
||||
urlParts[0] != 'playlist'
|
||||
) {
|
||||
throw new Error('URL unrecognised')
|
||||
}
|
||||
return [urlParts[0], urlParts[1]]
|
||||
@@ -494,6 +515,12 @@ export default class Tidal implements Streamer {
|
||||
type,
|
||||
metadata: await this.#getArtist(id)
|
||||
}
|
||||
case 'playlist':
|
||||
return {
|
||||
type,
|
||||
tracks: await this.#getPlaylistTracks(id),
|
||||
metadata: await this.#getPlaylist(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
async getAccountInfo(): Promise<StreamerAccount> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Album, Artist, Track } from '../../types.js'
|
||||
import { Album, Artist, Playlist, Track } from '../../types.js'
|
||||
import { DOMParser } from 'xmldom-qsa'
|
||||
|
||||
export interface RawArtist {
|
||||
@@ -78,6 +78,50 @@ export function parseAlbum(raw: RawAlbum): Album {
|
||||
return album
|
||||
}
|
||||
|
||||
export interface RawPlaylist {
|
||||
uuid: string
|
||||
title: string
|
||||
url: string
|
||||
numberOfTracks: number
|
||||
tracks: Track[]
|
||||
cover: string
|
||||
}
|
||||
|
||||
export function parsePlaylist(raw: RawPlaylist): Playlist {
|
||||
let coverPath
|
||||
|
||||
if (raw.cover) coverPath = raw.cover.replace(/-/gm, '/')
|
||||
else coverPath = null
|
||||
|
||||
const playlist: Playlist = {
|
||||
id: raw.uuid,
|
||||
title: raw.title,
|
||||
url: raw.url,
|
||||
trackCount: raw.numberOfTracks,
|
||||
tracks: []
|
||||
}
|
||||
|
||||
playlist.coverArtwork = [
|
||||
{
|
||||
url: `https://resources.tidal.com/images/${coverPath}/160x160.jpg`,
|
||||
width: 160,
|
||||
height: 160
|
||||
},
|
||||
{
|
||||
url: `https://resources.tidal.com/images/${coverPath}/320x320.jpg`,
|
||||
width: 320,
|
||||
height: 320
|
||||
},
|
||||
{
|
||||
url: `https://resources.tidal.com/images/${coverPath}/1280x1280.jpg`,
|
||||
width: 1280,
|
||||
height: 1280
|
||||
}
|
||||
]
|
||||
|
||||
return playlist
|
||||
}
|
||||
|
||||
export interface RawTrack {
|
||||
url: string
|
||||
id: number
|
||||
|
||||
17
src/types.ts
17
src/types.ts
@@ -1,6 +1,6 @@
|
||||
type Id = string | number
|
||||
|
||||
export type ItemType = 'artist' | 'album' | 'track' | 'episode' | 'podcast'
|
||||
export type ItemType = 'artist' | 'album' | 'track' | 'episode' | 'podcast' | 'playlist'
|
||||
export type Region = string
|
||||
|
||||
export interface CoverArtwork {
|
||||
@@ -18,6 +18,15 @@ export interface Artist {
|
||||
tracks?: Track[]
|
||||
}
|
||||
|
||||
export interface Playlist {
|
||||
id: Id
|
||||
title: string
|
||||
url: string
|
||||
coverArtwork?: CoverArtwork[]
|
||||
trackCount?: number
|
||||
tracks: Track[]
|
||||
}
|
||||
|
||||
export interface Album {
|
||||
title: string
|
||||
id: Id
|
||||
@@ -105,6 +114,7 @@ export type GetByUrlResponse =
|
||||
| AlbumGetByUrlResponse
|
||||
| EpisodeGetByUrlResponse
|
||||
| PodcastGetByUrlResponse
|
||||
| PlaylistGetByUrlResponse
|
||||
|
||||
export interface TrackGetByUrlResponse {
|
||||
type: 'track'
|
||||
@@ -125,6 +135,11 @@ export interface AlbumGetByUrlResponse {
|
||||
tracks: Track[]
|
||||
metadata: Album
|
||||
}
|
||||
export interface PlaylistGetByUrlResponse {
|
||||
type: 'playlist'
|
||||
tracks: Track[]
|
||||
metadata: Playlist
|
||||
}
|
||||
export interface PodcastGetByUrlResponse {
|
||||
type: 'podcast'
|
||||
episodes: Episode[]
|
||||
|
||||
Reference in New Issue
Block a user