backend/utils/ytdlp.js

158 lines
5.9 KiB
JavaScript
Raw Normal View History

2024-03-29 17:48:59 +00:00
const wget = require('wget-improved')
2023-09-30 16:32:32 +01:00
const DOMPurify = require('isomorphic-dompurify')
const metadata = require('./metadata.js')
2024-03-29 17:48:59 +00:00
const hr = require('@tsmx/human-readable')
2023-03-03 16:44:40 +00:00
2024-06-29 10:22:55 +01:00
const ffmpeg = require('fluent-ffmpeg')
const ffmpegStatic = require('ffmpeg-static')
const fs = require('node:fs')
ffmpeg.setFfmpegPath(ffmpegStatic)
async function downloadVideo(url, ws, id) {
return new Promise(async (resolve, reject) => {
2024-03-30 17:04:00 +00:00
let quality = '720'
const video = await metadata.getVideoMetadata(id)
if (video.error) {
2024-03-30 13:32:37 +00:00
return resolve({
message: `Failed to request Youtube with error ${video.error}. Please retry...`,
fail: true
})
}
2024-03-30 17:04:00 +00:00
if (video.basic_info.duration >= 900) quality = '360' // 15 minutes
2024-06-29 11:16:36 +01:00
const downloadJson = await metadata.getVideoDownload(video, quality)
2024-03-29 17:48:59 +00:00
let size = ''
2024-03-29 18:30:04 +00:00
let startTime = Date.now()
let prevBytes = 0
let speed = 0
2024-03-29 17:48:59 +00:00
const alreadyPrecentages = []
2024-06-10 19:05:37 +01:00
const target = Array.isArray(downloadJson.url) ? downloadJson.url[0] : downloadJson.url
2024-06-29 10:31:01 +01:00
const download = wget.download(target, `./videos/${id}.mp4`, {
proxy: {
protocol: 'http',
host: 'gluetun',
port: '8888',
headers: {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
accept: '*/*',
origin: 'https://www.youtube.com',
referer: 'https://www.youtube.com',
DNT: '?1'
}
}
})
2023-03-03 16:44:40 +00:00
2024-03-29 17:48:59 +00:00
download.on('start', fileSize => {
size = fileSize
2024-03-30 17:12:30 +00:00
if (ws) ws.send(`DATA - Download has started in ${quality}p`)
2023-03-03 16:44:40 +00:00
})
2024-03-29 17:48:59 +00:00
download.on('progress', progress => {
if (alreadyPrecentages.includes((progress*100).toFixed(0))) return
alreadyPrecentages.push((progress*100).toFixed(0))
2024-03-29 18:30:04 +00:00
const currentTime = Date.now()
const elapsedTime = (currentTime - startTime) / 1000
const currentBytes = progress * size
const bytesDownloaded = currentBytes - prevBytes
speed = bytesDownloaded / elapsedTime
prevBytes = currentBytes
const speedInMBps = speed / 1048576
const remainingBytes = size - currentBytes
const remainingTime = remainingBytes / speed
if (ws) ws.send(`DATA - [download] ${(progress*100).toFixed(2)}% of ${hr.fromBytes(size)} at ${speedInMBps.toFixed(2)} MB/s ETA ${secondsToTime(remainingTime.toFixed(0))}`)
})
2024-03-29 17:48:59 +00:00
download.on('error', err => {
if (ws) ws.send(`DATA - ${DOMPurify.sanitize(err)}`)
})
2024-03-29 17:48:59 +00:00
download.on('end', output => {
2024-06-29 10:22:55 +01:00
if (output !== 'Finished writing to disk') {
2024-03-30 13:32:37 +00:00
return resolve({
2024-06-29 10:22:55 +01:00
fail: true
2024-03-29 17:48:59 +00:00
})
2024-06-29 10:22:55 +01:00
}
if (Array.isArray(downloadJson.url)) { // this means video is separate
ws.send(`DATA - Downloading the video has finished, downloading audio.`);
2024-06-29 10:31:01 +01:00
const audioDownload = wget.download(downloadJson.url[1], `./videos/${id}_audio.mp4`, {
proxy: {
protocol: 'http',
host: 'gluetun',
port: '8888',
headers: {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
accept: '*/*',
origin: 'https://www.youtube.com',
referer: 'https://www.youtube.com',
DNT: '?1'
}
}
})
2024-06-29 10:22:55 +01:00
audioDownload.on('end', async (audioOutput) => {
if (audioOutput !== 'Finished writing to disk') {
return resolve({ fail: true });
}
fs.renameSync(`./videos/${id}.mp4`, `./videos/${id}_video.mp4`);
try {
await mergeIt(`./videos/${id}_audio.mp4`, `./videos/${id}_video.mp4`, `./videos/${id}.mp4`);
ws.send(`DATA - Merging succeeded.`)
2024-06-29 10:31:01 +01:00
fs.rmSync(`./videos/${id}_audio.mp4`)
fs.rmSync(`./videos/${id}_video.mp4`)
2024-06-29 10:22:55 +01:00
return resolve({ fail: false });
} catch (error) {
console.error('Merging error:', error);
return resolve({ fail: true });
}
});
audioDownload.on('error', (err) => {
console.error('Audio download error:', err);
return resolve({ fail: true });
});
2024-03-29 17:48:59 +00:00
} else {
2024-06-29 10:22:55 +01:00
ws.send(`DATA - Download has finished`)
2024-03-30 13:32:37 +00:00
return resolve({
2024-06-29 10:22:55 +01:00
fail: false
})
}
})
})
}
2024-06-29 10:22:55 +01:00
function mergeIt(audioPath, videoPath, outputPath) {
return new Promise((resolve, reject) => {
ffmpeg()
.addInput(videoPath)
.addInput(audioPath)
.outputOptions('-c:v copy')
.outputOptions('-c:a aac')
.output(outputPath)
.on('end', () => {
resolve('Merging finished!');
})
.on('error', (err) => {
reject(new Error('An error occurred: ' + err.message));
})
.run();
});
}
2024-03-29 18:30:04 +00:00
function secondsToTime(seconds) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
const formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds;
return `${minutes}:${formattedSeconds}`;
}
2023-03-03 16:44:40 +00:00
module.exports = { downloadVideo }