import {inject, injectable} from 'inversify'; import spotifyURI from 'spotify-uri'; import {SongMetadata, QueuedPlaylist, MediaSource} from '../services/player.js'; import {TYPES} from '../types.js'; import ffmpeg from 'fluent-ffmpeg'; import YoutubeAPI from './youtube-api.js'; import SpotifyAPI, {SpotifyTrack} from './spotify-api.js'; @injectable() export default class { private readonly youtubeAPI: YoutubeAPI; private readonly spotifyAPI: SpotifyAPI; constructor( @inject(TYPES.Services.YoutubeAPI) youtubeAPI: YoutubeAPI, @inject(TYPES.Services.SpotifyAPI) spotifyAPI: SpotifyAPI) { this.youtubeAPI = youtubeAPI; this.spotifyAPI = spotifyAPI; } async youtubeVideoSearch(query: string, shouldSplitChapters: boolean): Promise { return this.youtubeAPI.search(query, shouldSplitChapters); } async youtubeVideo(url: string, shouldSplitChapters: boolean): Promise { return this.youtubeAPI.getVideo(url, shouldSplitChapters); } async youtubePlaylist(listId: string, shouldSplitChapters: boolean): Promise { return this.youtubeAPI.getPlaylist(listId, shouldSplitChapters); } async spotifySource(url: string, playlistLimit: number, shouldSplitChapters: boolean): Promise<[SongMetadata[], number, number]> { const parsed = spotifyURI.parse(url); switch (parsed.type) { case 'album': { const [tracks, playlist] = await this.spotifyAPI.getAlbum(url, playlistLimit); return this.spotifyToYouTube(tracks, shouldSplitChapters, playlist); } case 'playlist': { const [tracks, playlist] = await this.spotifyAPI.getPlaylist(url, playlistLimit); return this.spotifyToYouTube(tracks, shouldSplitChapters, playlist); } case 'track': { const tracks = [await this.spotifyAPI.getTrack(url)]; return this.spotifyToYouTube(tracks, shouldSplitChapters); } case 'artist': { const tracks = await this.spotifyAPI.getArtist(url, playlistLimit); return this.spotifyToYouTube(tracks, shouldSplitChapters); } default: { return [[], 0, 0]; } } } async httpLiveStream(url: string): Promise { return new Promise((resolve, reject) => { ffmpeg(url).ffprobe((err, _) => { if (err) { reject(); } resolve({ url, source: MediaSource.HLS, isLive: true, title: url, artist: url, length: 0, offset: 0, playlist: null, thumbnailUrl: null, }); }); }); } private async spotifyToYouTube(tracks: SpotifyTrack[], shouldSplitChapters: boolean, playlist?: QueuedPlaylist | undefined): Promise<[SongMetadata[], number, number]> { const promisedResults = tracks.map(async track => this.youtubeAPI.search(`"${track.name}" "${track.artist}"`, shouldSplitChapters)); const searchResults = await Promise.allSettled(promisedResults); let nSongsNotFound = 0; // Count songs that couldn't be found const songs: SongMetadata[] = searchResults.reduce((accum: SongMetadata[], result) => { if (result.status === 'fulfilled') { for (const v of result.value) { accum.push({ ...v, ...(playlist ? {playlist} : {}), }); } } else { nSongsNotFound++; } return accum; }, []); return [songs, nSongsNotFound, tracks.length]; } }