fix(downloading): migrate from @distube/ytdl-core to ytdlp-nodejs yt downloading library
This commit is contained in:
@@ -21,7 +21,6 @@
|
||||
"gen": "nest generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"@distube/ytdl-core": "^4.16.12",
|
||||
"@nestjs/bullmq": "^10.2.3",
|
||||
"@nestjs/common": "^10.0.0",
|
||||
"@nestjs/config": "^3.2.2",
|
||||
@@ -42,7 +41,8 @@
|
||||
"rxjs": "^7.8.1",
|
||||
"spotify-url-info": "^3.2.18",
|
||||
"sqlite3": "^5.1.7",
|
||||
"yt-search": "^2.12.1"
|
||||
"yt-search": "^2.12.1",
|
||||
"ytdlp-nodejs": "^2.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
|
||||
@@ -3,46 +3,20 @@ import { TrackEntity } from '../track/track.entity';
|
||||
import { EnvironmentEnum } from '../environmentEnum';
|
||||
import { TrackService } from '../track/track.service';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as fs from 'fs';
|
||||
import * as ffmpeg from 'fluent-ffmpeg';
|
||||
import { YtDlp } from 'ytdlp-nodejs';
|
||||
import * as yts from 'yt-search';
|
||||
import * as ytdl from '@distube/ytdl-core';
|
||||
import { Readable } from 'stream';
|
||||
const NodeID3 = require('node-id3');
|
||||
|
||||
enum StreamStates {
|
||||
Finish = 'finish',
|
||||
Error = 'error',
|
||||
}
|
||||
|
||||
function parseCookies(
|
||||
cookieString?: string,
|
||||
): { name: string; value: string }[] | undefined {
|
||||
if (!cookieString) return undefined;
|
||||
return cookieString
|
||||
.split(';')
|
||||
.map((c) => {
|
||||
const [name, ...rest] = c.split('=');
|
||||
return { name: name.trim(), value: rest.join('=').trim() };
|
||||
})
|
||||
.filter((c) => c.name && c.value);
|
||||
}
|
||||
const HEADERS = {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class YoutubeService {
|
||||
private readonly logger = new Logger(TrackService.name);
|
||||
private readonly ytCookies: string | undefined;
|
||||
private readonly ytAgent: any | undefined;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
this.ytCookies = this.configService.get<string>('YT_COOKIES');
|
||||
if (this.ytCookies) {
|
||||
const cookiesArr = parseCookies(this.ytCookies);
|
||||
if (cookiesArr && cookiesArr.length > 0) {
|
||||
this.ytAgent = ytdl.createAgent(cookiesArr);
|
||||
}
|
||||
}
|
||||
}
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
async findOnYoutubeOne(artist: string, name: string): Promise<string> {
|
||||
this.logger.debug(`Searching ${artist} - ${name} on YT`);
|
||||
@@ -51,26 +25,31 @@ export class YoutubeService {
|
||||
return url;
|
||||
}
|
||||
|
||||
downloadAndFormat(track: TrackEntity, folderName: string): Promise<void> {
|
||||
async downloadAndFormat(
|
||||
track: TrackEntity,
|
||||
output: string,
|
||||
): Promise<void> {
|
||||
this.logger.debug(
|
||||
`Downloading ${track.artist} - ${track.name} (${track.youtubeUrl}) from YT`,
|
||||
);
|
||||
return new Promise((res, reject) => {
|
||||
ffmpeg(this.getYoutubeAudio(track.youtubeUrl, reject))
|
||||
.format(this.configService.get<string>(EnvironmentEnum.FORMAT))
|
||||
.on(StreamStates.Error, (err) => reject(err))
|
||||
.pipe(
|
||||
fs
|
||||
.createWriteStream(folderName)
|
||||
.on(StreamStates.Finish, () => {
|
||||
this.logger.debug(
|
||||
`Downloaded ${track.artist} - ${track.name} to ${folderName}`,
|
||||
);
|
||||
res();
|
||||
})
|
||||
.on(StreamStates.Error, (err) => reject(err)),
|
||||
);
|
||||
if (!track.youtubeUrl) {
|
||||
this.logger.error('youtubeUrl is null or undefined');
|
||||
throw Error('youtubeUrl is null or undefined');
|
||||
}
|
||||
const ytdlp = new YtDlp();
|
||||
await ytdlp.downloadAsync(track.youtubeUrl, {
|
||||
format: {
|
||||
filter: 'audioonly',
|
||||
type: this.configService.get<'m4a'>(EnvironmentEnum.FORMAT),
|
||||
quality: 0,
|
||||
},
|
||||
output,
|
||||
cookiesFromBrowser: this.configService.get<string>('YT_COOKIES'),
|
||||
headers: HEADERS,
|
||||
});
|
||||
this.logger.debug(
|
||||
`Downloaded ${track.artist} - ${track.name} to ${output}`,
|
||||
);
|
||||
}
|
||||
|
||||
async addImage(
|
||||
@@ -99,29 +78,4 @@ export class YoutubeService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private getYoutubeAudio(
|
||||
youtubeUrl: string,
|
||||
reject: (reason: any) => void,
|
||||
): Readable {
|
||||
if (!youtubeUrl) {
|
||||
this.logger.error('youtubeUrl is null or undefined');
|
||||
reject('youtubeUrl is null or undefined');
|
||||
return null;
|
||||
}
|
||||
const options: ytdl.downloadOptions = {
|
||||
quality: 'highestaudio',
|
||||
filter: 'audioonly',
|
||||
agent: this.ytAgent,
|
||||
requestOptions: {
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
},
|
||||
},
|
||||
};
|
||||
return ytdl(youtubeUrl, options).on(StreamStates.Error, (err) =>
|
||||
reject(err),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user