muse/src/services/get-songs.ts

110 lines
3.4 KiB
TypeScript

import {inject, injectable} from 'inversify';
import * as 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<SongMetadata[]> {
return this.youtubeAPI.search(query, shouldSplitChapters);
}
async youtubeVideo(url: string, shouldSplitChapters: boolean): Promise<SongMetadata[]> {
return this.youtubeAPI.getVideo(url, shouldSplitChapters);
}
async youtubePlaylist(listId: string, shouldSplitChapters: boolean): Promise<SongMetadata[]> {
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<SongMetadata> {
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];
}
}