2019-12-03 21:18:40 +01:00
'use strict'
const youtubedl = require ( 'youtube-dl' )
const fs = require ( 'fs' )
const ffmpeg = require ( 'fluent-ffmpeg' )
2019-12-24 16:19:04 +01:00
const { version } = require ( '../../../package.json' ) ;
2020-06-11 16:21:55 +02:00
const Ws = use ( 'Ws' ) ;
const Antl = use ( 'Antl' ) ;
2019-12-03 21:18:40 +01:00
let viewCounter = 0 ;
let files = [ ] ;
let day ;
let month ;
2020-01-24 01:58:43 +01:00
let announcementArray ;
2020-06-11 16:21:55 +02:00
let announcement ;
2020-01-23 18:38:51 +01:00
function formatBytes ( bytes , decimals = 2 ) { // https://stackoverflow.com/a/18650828
if ( bytes === 0 ) return '0 Bytes' ;
const k = 1024 ;
const dm = decimals < 0 ? 0 : decimals ;
const sizes = [ 'Bytes' , 'KB' , 'MB' , 'GB' , 'TB' , 'PB' , 'EB' , 'ZB' , 'YB' ] ;
const i = Math . floor ( Math . log ( bytes ) / Math . log ( k ) ) ;
return parseFloat ( ( bytes / Math . pow ( k , i ) ) . toFixed ( dm ) ) + ' ' + sizes [ i ] ;
}
2019-12-03 21:18:40 +01:00
class DownloadController {
2020-01-24 01:58:43 +01:00
async index ( { view , request , locale } ) {
2019-12-24 16:19:04 +01:00
viewCounter ++ ;
2020-01-24 01:58:43 +01:00
// Coudln't find a cleaner way to make it change with the browser locale
2020-06-11 16:21:55 +02:00
announcementArray = [ Antl . forLocale ( locale ) . formatMessage ( 'announcement.1' ) , Antl . forLocale ( locale ) . formatMessage ( 'announcement.2' ) , Antl . forLocale ( locale ) . formatMessage ( 'announcement.3' ) , Antl . forLocale ( locale ) . formatMessage ( 'announcement.4' ) , Antl . forLocale ( locale ) . formatMessage ( 'announcement.5' ) , Antl . forLocale ( locale ) . formatMessage ( 'announcement.6' ) , 'Playlist download is experimental' ] ;
2019-12-18 19:30:30 +01:00
// Get random announcement
announcement = announcementArray [ Math . floor ( Math . random ( ) * announcementArray . length ) ] ;
2019-12-03 21:18:40 +01:00
// Get date for some event
let today = new Date ( ) ;
day = today . getDay ( ) ;
month = today . getMonth ( ) ;
2019-12-24 16:19:04 +01:00
// If legacy link return
2020-01-24 22:03:21 +01:00
if ( request . url ( ) == '/legacy' ) return view . render ( 'legacy' , { version : version , viewCounter : viewCounter , day : day , month : month , announcement : announcement } ) ;
2020-06-11 16:21:55 +02:00
2019-12-03 21:18:40 +01:00
files = [ ] ;
2019-12-24 16:19:04 +01:00
let file = [ ] ;
2019-12-03 21:18:40 +01:00
for ( let f of fs . readdirSync ( './public/uploads' ) ) {
file . push ( f )
}
// get the 5 most recent files
2019-12-24 16:19:04 +01:00
file = file . sort ( ( a , b ) => {
2020-06-11 16:21:55 +02:00
if ( ( a || b ) . endsWith ( '.mp4' ) || ( a || b ) . endsWith ( '.webm' ) || ( a || b ) . endsWith ( '.mp3' ) || ( a || b ) . endsWith ( '.flac' ) || ( a || b ) . endsWith ( '.zip' ) ) {
2019-12-03 21:18:40 +01:00
let time1 = fs . statSync ( ` ./public/uploads/ ${ b } ` ) . ctime ;
2020-06-11 16:21:55 +02:00
let time2 = fs . statSync ( ` ./public/uploads/ ${ a } ` ) . ctime ;
2019-12-03 21:18:40 +01:00
if ( time1 < time2 ) return - 1 ;
if ( time1 > time2 ) return 1 ;
}
return 0 ;
} ) . slice ( 0 , 5 )
2020-06-11 16:21:55 +02:00
// Save space by deleting file that doesn't appear in the recent feed
2020-01-21 23:39:30 +01:00
for ( let f of fs . readdirSync ( './public/uploads' ) ) {
2020-06-11 16:21:55 +02:00
if ( ! file . includes ( f ) && ( f != 'hidden' && f != '.keep' && f != 'playlist' ) ) {
2020-01-21 23:39:30 +01:00
fs . unlinkSync ( ` ./public/uploads/ ${ f } ` ) ;
}
}
2020-06-11 16:21:55 +02:00
for ( let f of file ) {
2019-12-24 16:19:04 +01:00
if ( f . endsWith ( '.mp4' ) || f . endsWith ( '.webm' ) ) {
2019-12-19 15:20:00 +01:00
// Send file name, file size in MB relative path for the file
2020-01-26 02:38:56 +01:00
let fileInfo = formatBytes ( fs . statSync ( ` ./public/uploads/ ${ f } ` ) . size ) . split ( ' ' ) ;
2020-01-26 03:15:22 +01:00
files . push ( { name : f . split ( '.' ) . slice ( 0 , - 1 ) . join ( '.' ) , size : fileInfo [ 0 ] , unit : fileInfo [ 1 ] , date : fs . statSync ( ` ./public/uploads/ ${ f } ` ) . ctime , location : ` uploads/ ${ f } ` , ext : f . split ( '.' ) . pop ( ) , img : '' } ) ;
2019-12-24 16:19:04 +01:00
} else if ( f . endsWith ( '.mp3' ) || f . endsWith ( '.flac' ) ) {
2019-12-19 15:14:54 +01:00
// Send file name, file size in MB relative path for the file and relative path of music.png
2020-01-26 02:38:56 +01:00
let fileInfo = formatBytes ( fs . statSync ( ` ./public/uploads/ ${ f } ` ) . size ) . split ( ' ' ) ;
2020-01-26 03:15:22 +01:00
files . push ( { name : f . split ( '.' ) . slice ( 0 , - 1 ) . join ( '.' ) , size : fileInfo [ 0 ] , unit : fileInfo [ 1 ] , date : fs . statSync ( ` ./public/uploads/ ${ f } ` ) . ctime , location : ` uploads/ ${ f } ` , ext : f . split ( '.' ) . pop ( ) , img : ` /asset/music.png ` } ) ;
2019-12-03 21:18:40 +01:00
}
2019-12-24 16:19:04 +01:00
}
2020-06-11 16:21:55 +02:00
2020-01-24 22:03:21 +01:00
return view . render ( 'index' , { version : version , viewCounter : viewCounter , file : files , day : day , month : month , announcement : announcement } ) ;
2019-12-03 21:18:40 +01:00
}
async download ( { view , request , response } ) {
2020-06-11 16:21:55 +02:00
const ws = Ws . getChannel ( 'progress' ) . topic ( 'progress' ) ;
2019-12-03 21:18:40 +01:00
// To be honest i forgot what it does, but i think i need it
response . implicitEnd = false
let option , DLFile
// Get form input
let data = {
url : request . input ( 'URL' ) ,
quality : request . input ( 'quality' ) ,
format : request . input ( 'format' ) ,
alt : request . input ( 'alt' ) ,
feed : request . input ( 'feed' )
}
if ( ! data . url ) {
2020-06-11 16:21:55 +02:00
if ( ws ) {
ws . socket . emit ( 'error' , 'bruh moment, you didin\'t input a link.' ) ;
}
return ;
2019-12-03 21:18:40 +01:00
}
// Youtube-dl quality settings
if ( data . quality == 'small' )
option = 'worst'
else
option = 'best'
// If alt download ( Quality settings and file format option doesn't work here )
if ( data . alt ) {
2020-01-16 18:13:27 +01:00
let altFolder ;
if ( data . feed == 'on' ) {
altFolder = './public/uploads/hidden/alt.mp4' ;
} else {
altFolder = './public/uploads/alt.mp4'
}
if ( fs . existsSync ( altFolder ) ) {
fs . unlink ( altFolder , ( err ) => {
2019-12-03 21:18:40 +01:00
if ( err ) ;
} ) ;
}
2020-06-11 16:21:55 +02:00
2020-01-16 18:13:27 +01:00
return youtubedl . exec ( data . url , [ '--format=mp4' , '-o' , altFolder ] , { } , function ( err , output ) {
2019-12-03 21:18:40 +01:00
if ( err ) {
2020-01-23 16:18:35 +01:00
console . error ( err ) ;
2020-06-11 16:21:55 +02:00
if ( ws ) {
ws . socket . emit ( 'error' , err . toString ( ) ) ;
}
return ;
2019-12-03 21:18:40 +01:00
}
2020-06-11 16:21:55 +02:00
console . log ( altFolder . slice ( 17 ) )
if ( ws ) {
ws . socket . emit ( 'end' , altFolder . slice ( 17 ) ) ;
}
return ;
2019-12-03 21:18:40 +01:00
} ) ;
} else {
2020-06-11 16:21:55 +02:00
if ( data . url . match ( /^.*(youtu.be\/|list=)([^#\&\?]*).*/ ) ) {
playlistDownload ( data )
} else {
// Download as mp4 if possible
let video = youtubedl ( data . url , [ '--format=mp4' , '-f' , option ] ) ;
video . on ( 'error' , function ( err ) {
console . error ( err ) ;
if ( ws ) {
ws . socket . emit ( 'error' , err . toString ( ) ) ;
}
return ;
} )
let ext ;
let size = 0
video . on ( 'info' , function ( info ) {
size = info . size
// Set file name
ext = info . ext ;
let title = info . title . slice ( 0 , 50 ) ;
DLFile = ` ${ title . replace ( /\s/g , '_' ) } . ${ ext } ` ;
DLFile = DLFile . replace ( /[()]|[/]|[\\]|[?]|[!]/g , '_' ) ;
// If no title use the ID
if ( title == '_' ) title = ` _ ${ info . id } ` ;
// If user want to hide from the feed
if ( data . feed == 'on' )
DLFile = ` hidden/ ${ title } . ${ ext } ` ;
video . pipe ( fs . createWriteStream ( ` ./public/uploads/ ${ DLFile } ` ) ) ;
} ) ;
let pos = 0
video . on ( 'data' , function data ( chunk ) {
pos += chunk . length
// `size` should not be 0 here.
if ( size ) {
let percent = ( pos / size * 100 ) . toFixed ( 2 )
if ( ws ) {
ws . socket . emit ( 'progress' , percent ) ;
}
}
} )
2019-12-03 21:18:40 +01:00
2020-06-11 16:21:55 +02:00
video . on ( 'end' , function ( ) {
console . log ( 'end' ) ;
if ( ws ) {
ws . socket . emit ( 'message' , 'end' ) ;
}
if ( data . format == 'mp4' || data . format == 'webm' ) {
// If user requested mp4 directly attach the file
if ( ws ) {
ws . socket . emit ( 'end' , DLFile ) ;
}
return ;
} else {
// If user requested an audio format, convert it
ffmpeg ( ` ./public/uploads/ ${ DLFile } ` )
. noVideo ( )
. audioChannels ( '2' )
. audioFrequency ( '44100' )
. audioBitrate ( '320k' )
. format ( data . format )
. save ( ` ./public/uploads/ ${ DLFile . replace ( ` . ${ ext } ` , ` . ${ data . format } ` ) } ` )
. on ( 'progress' , ( progress ) => {
wb . broadcast ( progress . percent )
} )
. on ( 'end' , ( ) => {
fs . unlinkSync ( ` ./public/uploads/ ${ DLFile } ` ) ;
if ( ws ) {
ws . socket . emit ( 'end' , DLFile . replace ( ` . ${ ext } ` , ` . ${ data . format } ` ) ) ;
}
} ) ;
}
} ) ;
}
}
function playlistDownload ( data ) {
const video = youtubedl ( data . url )
video . on ( 'error' , function error ( err ) {
2020-01-23 16:18:35 +01:00
console . error ( err ) ;
2020-06-11 16:21:55 +02:00
if ( ws ) {
ws . socket . emit ( 'error' , err . toString ( ) ) ;
}
return ;
} ) ;
2019-12-03 21:18:40 +01:00
2019-12-18 19:30:30 +01:00
let ext ;
2020-06-11 16:21:55 +02:00
let size = 0
2019-12-03 21:18:40 +01:00
video . on ( 'info' , function ( info ) {
2020-06-11 16:21:55 +02:00
console . log ( info ) ;
size = info . size
2019-12-03 21:18:40 +01:00
// Set file name
2019-12-18 19:30:30 +01:00
ext = info . ext ;
2020-01-16 18:13:27 +01:00
let title = info . title . slice ( 0 , 50 ) ;
2019-12-18 19:30:30 +01:00
DLFile = ` ${ title . replace ( /\s/g , '_' ) } . ${ ext } ` ;
2020-06-11 16:21:55 +02:00
DLFile = DLFile . replace ( /[()]|[/]|[\\]|[?]|[!]/g , '_' ) ;
2019-12-15 13:52:53 +01:00
2019-12-03 21:18:40 +01:00
// If no title use the ID
if ( title == '_' ) title = ` _ ${ info . id } ` ;
2020-06-11 16:21:55 +02:00
// If user want to hide from the feed
if ( data . feed == 'on' )
DLFile = ` hidden/playlist/ ${ title } . ${ ext } ` ;
2019-12-03 21:18:40 +01:00
2020-06-11 16:21:55 +02:00
video . pipe ( fs . createWriteStream ( ` ./public/uploads/playlist/ ${ DLFile } ` ) ) ;
} ) ;
let pos = 0
video . on ( 'data' , function data ( chunk ) {
pos += chunk . length
// `size` should not be 0 here.
if ( size ) {
let percent = ( pos / size * 100 ) . toFixed ( 2 )
process . stdout . cursorTo ( 0 )
process . stdout . clearLine ( 1 )
if ( ws ) {
ws . socket . emit ( 'progress' , percent ) ;
}
}
2019-12-03 21:18:40 +01:00
} ) ;
video . on ( 'end' , function ( ) {
2020-06-11 16:21:55 +02:00
if ( data . format == 'mp3' || data . format == 'flac' ) {
2019-12-03 21:18:40 +01:00
// If user requested an audio format, convert it
2020-06-11 16:21:55 +02:00
ffmpeg ( ` ./public/uploads/playlist/ ${ DLFile } ` )
2019-12-03 21:18:40 +01:00
. noVideo ( )
. audioChannels ( '2' )
. audioFrequency ( '44100' )
. audioBitrate ( '320k' )
. format ( data . format )
2020-06-11 16:21:55 +02:00
. save ( ` ./public/uploads/playlist/ ${ DLFile . replace ( ` . ${ ext } ` , ` . ${ data . format } ` ) } ` )
. on ( 'progress' , ( progress ) => {
ws . socket . emit ( progress . percent )
2019-12-03 21:18:40 +01:00
} )
2020-06-11 16:21:55 +02:00
. on ( 'end' , ( ) => {
fs . unlinkSync ( ` ./public/uploads/playlist/ ${ DLFile } ` ) ;
if ( ws ) {
ws . socket . emit ( 'end' , ` ./public/uploads/playlist/ ${ DLFile . replace ( ` . ${ ext } ` , ` . ${ data . format } ` ) } ` ) ;
}
} ) ;
} else {
if ( ws ) {
ws . socket . emit ( 'end' , ` ./public/uploads/playlist/ ${ DLFile } ` ) ;
}
2019-12-03 21:18:40 +01:00
}
} ) ;
2020-06-11 16:21:55 +02:00
video . on ( 'next' , playlistDownload ) ;
2019-12-03 21:18:40 +01:00
}
}
}
module . exports = DownloadController