import {Injectable} from '@nestjs/common'; import {InjectRepository} from '@nestjs/typeorm'; import {Repository} from 'typeorm'; import {TrackEntity} from "./track.entity"; import {PlaylistEntity} from "../playlist/playlist.entity"; import {Interval} from "@nestjs/schedule"; import {TrackStatusEnum} from "./track.model"; import * as yts from 'yt-search'; import * as ytdl from '@distube/ytdl-core'; import * as fs from 'fs'; import {ConfigService} from "@nestjs/config"; import {resolve} from "path"; import {WebSocketGateway, WebSocketServer} from "@nestjs/websockets"; import {Server} from "socket.io"; import {SearchResult} from "yt-search"; @WebSocketGateway() @Injectable() export class TrackService { @WebSocketServer() io: Server; constructor( @InjectRepository(TrackEntity) private repository: Repository, private readonly configService: ConfigService, ) {} findAll(criteria?: Partial): Promise { return this.repository.find({where: criteria}); } getAllByPlaylist(id: number): Promise { return this.repository.find({where: {playlist: {id}}}); } findOne(id: number): Promise { return this.repository.findOneBy({ id }); } async remove(id: number): Promise { await this.repository.delete(id); } async create(track: TrackEntity, playlist?: PlaylistEntity): Promise { const savedTrack = await this.repository.save({...track, playlist}); this.io.emit('trackNew', {track: savedTrack, playlistId: playlist.id}); } async update(id: number, track: TrackEntity): Promise { await this.repository.update(id, track); this.io.emit('trackUpdate', track); } @Interval(1000) async findOnYoutube() { const newTracks = await this.findAll({status: TrackStatusEnum.New}); for (let track of newTracks) { await this.update(track.id, {...track, status: TrackStatusEnum.Searching}); } for(let track of newTracks) { let youtubeResult: SearchResult; let updatedTrack: TrackEntity; try { youtubeResult = await yts(`${track.artist} - ${track.song}`); updatedTrack = {...track, youtubeUrl: youtubeResult.videos[0].url, status: TrackStatusEnum.Queued}; } catch (err) { updatedTrack = {...track, error: String(err), status: TrackStatusEnum.Error}; } await this.update(track.id, updatedTrack); } } @Interval(1000) async download() { const queuedTracks = await this.findAll({status: TrackStatusEnum.Queued}); for (let track of queuedTracks) { await this.update(track.id, {...track, status: TrackStatusEnum.Downloading}); } for(let track of queuedTracks) { let error: string; try { await this.youtubeDownload(track); } catch(err) { console.log(err); error = String(err); } const updatedTrack = { ...track, status: error ? TrackStatusEnum.Error : TrackStatusEnum.Completed, ...(error ? {error} : {}), }; await this.update(track.id, updatedTrack); } } private youtubeDownload(track: TrackEntity): Promise { return new Promise((res, reject) => ytdl(track.youtubeUrl, {quality: "highestaudio", filter: "audioonly"}) .on('error', (err) => reject(err)).pipe( fs.createWriteStream(resolve(__dirname, '..', this.configService.get('DOWNLOADS'), `${track.artist} - ${track.song.replace('/', '')}.mp3`)) .on('finish', () => res()) .on('error', (err) => reject(err)) ) ); } }