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' )
2024-03-29 14:32:12 +00:00
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 )
2024-03-29 14:32:12 +00:00
async function downloadVideo ( url , ws , id ) {
return new Promise ( async ( resolve , reject ) => {
2024-03-30 17:04:00 +00:00
let quality = '720'
2024-03-29 14:32:12 +00:00
const video = await metadata . getVideoMetadata ( id )
2024-03-30 10:11:49 +00:00
if ( video . error ) {
2024-03-30 13:32:37 +00:00
return resolve ( {
2024-03-30 10:11:49 +00:00
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-03-30 10:11:49 +00:00
2024-06-29 11:16:36 +01:00
const downloadJson = await metadata . getVideoDownload ( video , quality )
2024-03-29 14:32:12 +00:00
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 14:32:12 +00:00
} )
2024-03-29 17:48:59 +00:00
download . on ( 'error' , err => {
if ( ws ) ws . send ( ` DATA - ${ DOMPurify . sanitize ( err ) } ` )
2024-03-29 14:32:12 +00:00
} )
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-03-29 14:32:12 +00:00
} )
}
} )
} )
}
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 }