我正在开发 YouTube 播放列表音频下载器,但遇到了障碍(见下文)。下载一个视频的音频没有问题;我只是将流传输到响应:
ytdl(info.videoDetails.videoId, {
quality: "highestaudio",
filter: "audioonly",
}).pipe(res);
我想做的是发送播放列表中每个视频的音频。我正在考虑将响应作为“多部分/表单数据”发送,其中每个表单段都包含作为 blob 的音频,如本文中所述。
我在将 ytdl-core 的响应转换为 blob 时遇到问题。 我认为我正在转换流本身,而不是音频数据。
我收到的错误是:
C:\Users\blackjacques\youtube-audio-downloader-master\node_modules\delayed-stream\lib\delayed_stream.js:33
source.on('error', function() {});
^
TypeError: source.on is not a function
at DelayedStream.create (C:\Users\blackjacques\youtube-audio-downloader-master\node_modules\delayed-stream\lib\delayed_stream.js:33:10)
at CombinedStream.append (C:\Users\blackjacques\youtube-audio-downloader-master\node_modules\combined-stream\lib\combined_stream.js:45:37)
at FormData.append (C:\Users\blackjacques\youtube-audio-downloader-master\node_modules\form-data\lib\form_data.js:75:3)
at C:\Users\blackjacques\youtube-audio-downloader-master\server.js:169:12
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
Node.js v22.11.0
我正在使用“@distube/ytdl-core”:“^4.15.1”,“cors”:“^2.8.5”,“express”:“^4.17.1”,“form-data”:“^ 4.0.1”和“ytpl”:“^2.3.0”。
以下是相关的nodejsexpress服务器代码:
getListOfSongsFromPlaylist = async (playlistUrl) => {
console.log(playlistUrl)
let playlist = await ytpl(playlistUrl);
playlist.items = playlist.items.filter(song => song.isPlayable === true).map(song => {
const title = (song.title.includes(song.author.name)) ? song.title : song.author.name + ' - ' + song.title;
return {
id: song.id,
title: title,
duration: song.duration
}
}
);
return playlist;
}
getAudioAsBlob = async (videoId) => {
return await new Response(
ytdl(videoId, {
quality: "highestaudio",
filter: "audioonly",
}).blob();
};
app.post("/", async (req, res) => {
console.log(`Request: ${req.url}`);
const form = new FormData();
getListOfSongsFromPlaylist(req.body.url).then(async playlist => {
console.log('Playlist name: ' + playlist.title);
console.log('Total songs in playlist: ' + playlist.estimatedItemCount);
playlist.items.forEach(async song => {
const audioAsBlob = await getAudioAsBlob(song.id);
form.append(song.title, audioAsBlob, {
filename: `${song.title}.mp3`,
contentType: 'audio/mpeg',
knownLength: audioAsBlob.size,
});
});
});
// Set headers for multipart response.
res.writeHead(200, {
'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`, // boundary must match the one generated by form-data
'Content-Length': form.getLengthSync(),
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Origin': '*',
});
// Write the multipart form data response.
res.write(form.getBuffer());
res.end();
});
关于如何将 ytdl-core 数据转换为 blob 有什么想法吗?
我终于弄清楚如何将音频流转换为 blob。诀窍是在“data”事件中将数据推送到数组中。我最终将数据数组转换为缓冲区,因为 FormData 不喜欢 blob(这是一个已知问题)。这是在“结束”事件中完成的:
const getAudioAsBuffer = async (videoId) => {
return new Promise((resolve, reject) => {
const chunks = [];
ytdl(videoId, {
quality: "highestaudio",
filter: "audioonly",
}).on('data', (data) => {
chunks.push(data);
}).on('error', (err) => {
console.log("error: " + err.message);
reject(err.message);
}).on('end', async () => {
const arrayBuffer = await new Blob(chunks).arrayBuffer();
resolve(Buffer.from(arrayBuffer));
});
});
};
然后只需将缓冲区附加到表单即可:
const form = new FormData();
const buffer = await getAudioAsBuffer(song.id);
form.append(song.title, buffer, {
filename: `${song.title}.mp3`,
contentType: 'audio/mpeg',
knownLength: buffer.length,
});
// Set headers for multipart response.
res.writeHead(200, {
'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`, // boundary must match the one generated by form-data
'Content-Length': form.getLengthSync(),
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Origin': '*',
});
// Write the multipart form data response.
res.write(form.getBuffer());
res.end();