fix(downloading): migrate from @distube/ytdl-core to ytdlp-nodejs yt downloading library

This commit is contained in:
Raiper34
2025-11-15 15:05:31 +00:00
parent 4be3743f7b
commit 5795f7cc17
5 changed files with 48 additions and 82 deletions
+2 -2
View File
@@ -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",
+27 -73
View File
@@ -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),
);
}
}