[tidal] add playlists support

This commit is contained in:
hazycora
2024-08-02 17:56:15 -04:00
parent 99df8075b1
commit 8b2768c8d6
3 changed files with 90 additions and 4 deletions

View File

@@ -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> {

View File

@@ -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

View File

@@ -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[]