@ -77,6 +77,7 @@ db.defaults(
video : [ ]
} ,
configWriteFlag : false ,
downloads : { } ,
subscriptions : [ ] ,
pin _md5 : '' ,
files _to _db _migration _complete : false
@ -101,6 +102,10 @@ var archivePath = path.join(__dirname, 'appdata', 'archives');
var options = null ; // encryption options
var url _domain = null ;
var updaterStatus = null ;
var last _downloads _check = null ;
var downloads _check _interval = 1000 ;
var timestamp _server _start = Date . now ( ) ;
if ( debugMode ) logger . info ( 'YTDL-Material in debug mode!' ) ;
@ -141,6 +146,7 @@ if (writeConfigMode) {
loadConfig ( ) ;
}
var downloads = { } ;
var descriptors = { } ;
app . use ( bodyParser . urlencoded ( { extended : false } ) ) ;
@ -233,7 +239,6 @@ async function startServer() {
logger . info ( ` YoutubeDL-Material ${ CONSTS [ 'CURRENT_VERSION' ] } started on PORT ${ backendPort } ` ) ;
} ) ;
}
}
async function restartServer ( ) {
@ -546,6 +551,9 @@ async function loadConfig() {
// check migrations
await checkMigrations ( ) ;
// load in previous downloads
downloads = db . get ( 'downloads' ) . value ( ) ;
// start the server here
startServer ( ) ;
@ -997,9 +1005,18 @@ function registerFileDB(full_file_path, type) {
return false ;
}
// add additional info
file _object [ 'uid' ] = uuid ( ) ;
file _object [ 'registered' ] = Date . now ( ) ;
path _object = path . parse ( file _object [ 'path' ] ) ;
file _object [ 'path' ] = path . format ( path _object ) ;
// remove existing video if overwriting
db . get ( ` files. ${ type } ` )
. remove ( {
path : file _object [ 'path' ]
} ) . write ( ) ;
db . get ( ` files. ${ type } ` )
. push ( file _object )
. write ( ) ;
@ -1013,6 +1030,7 @@ function generateFileObject(id, type) {
}
const ext = ( type === 'audio' ) ? '.mp3' : '.mp4'
const file _path = getTrueFileName ( jsonobj [ '_filename' ] , type ) ; // path.join(type === 'audio' ? audioFolderPath : videoFolderPath, id + ext);
// console.
var stats = fs . statSync ( path . join ( _ _dirname , file _path ) ) ;
var title = jsonobj . title ;
@ -1080,6 +1098,332 @@ function getVideoInfos(fileNames) {
return result ;
}
// downloads
async function downloadFileByURL _exec ( url , type , options , sessionID = null ) {
return new Promise ( async resolve => {
var date = Date . now ( ) ;
// audio / video specific vars
var is _audio = type === 'audio' ;
var ext = is _audio ? '.mp3' : '.mp4' ;
var fileFolderPath = type === 'audio' ? audioFolderPath : videoFolderPath ;
const downloadConfig = await generateArgs ( url , type , options ) ;
// adds download to download helper
const download _uid = uuid ( ) ;
const session = sessionID ? sessionID : 'undeclared' ;
if ( ! downloads [ session ] ) downloads [ session ] = { } ;
downloads [ session ] [ download _uid ] = {
uid : download _uid ,
ui _uid : options . ui _uid ,
downloading : true ,
complete : false ,
url : url ,
type : type ,
percent _complete : 0 ,
is _playlist : url . includes ( 'playlist' ) ,
timestamp _start : Date . now ( )
} ;
const download = downloads [ session ] [ download _uid ] ;
updateDownloads ( ) ;
youtubedl . exec ( url , downloadConfig , { } , function ( err , output ) {
download [ 'downloading' ] = false ;
download [ 'timestamp_end' ] = Date . now ( ) ;
var file _uid = null ;
let new _date = Date . now ( ) ;
let difference = ( new _date - date ) / 1000 ;
logger . debug ( ` ${ is _audio ? 'Audio' : 'Video' } download delay: ${ difference } seconds. ` ) ;
if ( err ) {
logger . error ( err . stderr ) ;
download [ 'error' ] = err . stderr ;
updateDownloads ( ) ;
resolve ( false ) ;
throw err ;
} else if ( output ) {
if ( output . length === 0 || output [ 0 ] . length === 0 ) {
download [ 'error' ] = 'No output. Check if video already exists in your archive.' ;
updateDownloads ( ) ;
resolve ( false ) ;
return ;
}
var file _names = [ ] ;
for ( let i = 0 ; i < output . length ; i ++ ) {
let output _json = null ;
try {
output _json = JSON . parse ( output [ i ] ) ;
} catch ( e ) {
output _json = null ;
}
var modified _file _name = output _json ? output _json [ 'title' ] : null ;
if ( ! output _json ) {
continue ;
}
// get filepath with no extension
const filepath _no _extension = removeFileExtension ( output _json [ '_filename' ] ) ;
var full _file _path = filepath _no _extension + ext ;
var file _name = filepath _no _extension . substring ( fileFolderPath . length , filepath _no _extension . length ) ;
// renames file if necessary due to bug
if ( ! fs . existsSync ( output _json [ '_filename' ] && fs . existsSync ( output _json [ '_filename' ] + '.webm' ) ) ) {
try {
fs . renameSync ( output _json [ '_filename' ] + '.webm' , output _json [ '_filename' ] ) ;
logger . info ( 'Renamed ' + file _name + '.webm to ' + file _name ) ;
} catch ( e ) {
}
}
if ( type === 'audio' ) {
let tags = {
title : output _json [ 'title' ] ,
artist : output _json [ 'artist' ] ? output _json [ 'artist' ] : output _json [ 'uploader' ]
}
let success = NodeID3 . write ( tags , output _json [ '_filename' ] ) ;
if ( ! success ) logger . error ( 'Failed to apply ID3 tag to audio file ' + output _json [ '_filename' ] ) ;
}
// registers file in DB
file _uid = registerFileDB ( full _file _path . substring ( fileFolderPath . length , full _file _path . length ) , type ) ;
if ( file _name ) file _names . push ( file _name ) ;
}
let is _playlist = file _names . length > 1 ;
if ( options . merged _string ) {
let current _merged _archive = fs . readFileSync ( fileFolderPath + 'merged.txt' , 'utf8' ) ;
let diff = current _merged _archive . replace ( options . merged _string , '' ) ;
const archive _path = path . join ( archivePath , ` archive_ ${ type } .txt ` ) ;
fs . appendFileSync ( archive _path , diff ) ;
}
download [ 'complete' ] = true ;
updateDownloads ( ) ;
var videopathEncoded = encodeURIComponent ( file _names [ 0 ] ) ;
resolve ( {
[ ( type === 'audio' ) ? 'audiopathEncoded' : 'videopathEncoded' ] : videopathEncoded ,
file _names : is _playlist ? file _names : null ,
uid : file _uid
} ) ;
}
} ) ;
} ) ;
}
async function downloadFileByURL _normal ( url , type , options , sessionID = null ) {
return new Promise ( async resolve => {
var date = Date . now ( ) ;
var file _uid = null ;
var fileFolderPath = type === 'audio' ? audioFolderPath : videoFolderPath ;
const downloadConfig = await generateArgs ( url , type , options ) ;
// adds download to download helper
const download _uid = uuid ( ) ;
const session = sessionID ? sessionID : 'undeclared' ;
if ( ! downloads [ session ] ) downloads [ session ] = { } ;
downloads [ session ] [ download _uid ] = {
uid : download _uid ,
ui _uid : options . ui _uid ,
downloading : true ,
complete : false ,
url : url ,
type : type ,
percent _complete : 0 ,
is _playlist : url . includes ( 'playlist' ) ,
timestamp _start : Date . now ( )
} ;
const download = downloads [ session ] [ download _uid ] ;
updateDownloads ( ) ;
const video = youtubedl ( url ,
// Optional arguments passed to youtube-dl.
downloadConfig ,
// Additional options can be given for calling `child_process.execFile()`.
{ cwd : _ _dirname } ) ;
let video _info = null ;
let file _size = 0 ;
// Will be called when the download starts.
video . on ( 'info' , function ( info ) {
video _info = info ;
file _size = video _info . size ;
fs . writeJSONSync ( removeFileExtension ( video _info . _filename ) + '.info.json' , video _info ) ;
video . pipe ( fs . createWriteStream ( video _info . _filename , { flags : 'w' } ) )
} ) ;
// Will be called if download was already completed and there is nothing more to download.
video . on ( 'complete' , function complete ( info ) {
'use strict'
logger . info ( 'file ' + info . _filename + ' already downloaded.' )
} )
let download _pos = 0 ;
video . on ( 'data' , function data ( chunk ) {
download _pos += chunk . length
// `size` should not be 0 here.
if ( file _size ) {
let percent = ( download _pos / file _size * 100 ) . toFixed ( 2 )
download [ 'percent_complete' ] = percent ;
}
} ) ;
video . on ( 'end' , function ( ) {
let new _date = Date . now ( ) ;
let difference = ( new _date - date ) / 1000 ;
logger . debug ( ` Video download delay: ${ difference } seconds. ` ) ;
download [ 'complete' ] = true ;
updateDownloads ( ) ;
// audio-only cleanup
if ( type === 'audio' ) {
// filename fix
video _info [ '_filename' ] = removeFileExtension ( video _info [ '_filename' ] ) + '.mp3' ;
// ID3 tagging
let tags = {
title : video _info [ 'title' ] ,
artist : video _info [ 'artist' ] ? video _info [ 'artist' ] : video _info [ 'uploader' ]
}
let success = NodeID3 . write ( tags , video _info . _filename ) ;
if ( ! success ) logger . error ( 'Failed to apply ID3 tag to audio file ' + video _info . _filename ) ;
}
// registers file in DB
const base _file _name = video _info . _filename . substring ( fileFolderPath . length , video _info . _filename . length ) ;
file _uid = registerFileDB ( base _file _name , type ) ;
if ( options . merged _string ) {
let current _merged _archive = fs . readFileSync ( fileFolderPath + 'merged.txt' , 'utf8' ) ;
let diff = current _merged _archive . replace ( options . merged _string , '' ) ;
const archive _path = path . join ( archivePath , 'archive_video.txt' ) ;
fs . appendFileSync ( archive _path , diff ) ;
}
videopathEncoded = encodeURIComponent ( removeFileExtension ( base _file _name ) ) ;
resolve ( {
[ ( type === 'audio' ) ? 'audiopathEncoded' : 'videopathEncoded' ] : videopathEncoded ,
file _names : /*is_playlist ? file_names :*/ null , // playlist support is not ready
uid : file _uid
} ) ;
} ) ;
video . on ( 'error' , function error ( err ) {
logger . error ( err ) ;
download [ error ] = err ;
updateDownloads ( ) ;
resolve ( false ) ;
} ) ;
} ) ;
}
async function generateArgs ( url , type , options ) {
return new Promise ( async resolve => {
var videopath = '%(title)s' ;
var globalArgs = config _api . getConfigItem ( 'ytdl_custom_args' ) ;
var is _audio = type === 'audio' ;
var fileFolderPath = is _audio ? audioFolderPath : videoFolderPath ;
var customArgs = options . customArgs ;
var customOutput = options . customOutput ;
var customQualityConfiguration = options . customQualityConfiguration ;
// video-specific args
var selectedHeight = options . selectedHeight ;
// audio-specific args
var maxBitrate = options . maxBitrate ;
var youtubeUsername = options . youtubeUsername ;
var youtubePassword = options . youtubePassword ;
let downloadConfig = null ;
let qualityPath = is _audio ? '-f bestaudio' : '-f best[ext=mp4]' ;
if ( ! is _audio && ( url . includes ( 'tiktok' ) || url . includes ( 'pscp.tv' ) ) ) {
// tiktok videos fail when using the default format
qualityPath = '-f best' ;
}
if ( customArgs ) {
downloadConfig = customArgs . split ( ' ' ) ;
} else {
if ( customQualityConfiguration ) {
qualityPath = customQualityConfiguration ;
} else if ( selectedHeight && selectedHeight !== '' && ! is _audio ) {
qualityPath = ` -f bestvideo[height= ${ selectedHeight } ]+bestaudio/best[height= ${ selectedHeight } ] ` ;
} else if ( maxBitrate && is _audio ) {
qualityPath = ` --audio-quality ${ maxBitrate } `
}
if ( customOutput ) {
downloadConfig = [ '-o' , fileFolderPath + customOutput + "" , qualityPath , '--write-info-json' , '--print-json' ] ;
} else {
downloadConfig = [ '-o' , fileFolderPath + videopath + ( is _audio ? '.%(ext)s' : '.mp4' ) , qualityPath , '--write-info-json' , '--print-json' ] ;
}
if ( is _audio ) {
downloadConfig . push ( '-x' ) ;
downloadConfig . push ( '--audio-format' , 'mp3' ) ;
}
if ( youtubeUsername && youtubePassword ) {
downloadConfig . push ( '--username' , youtubeUsername , '--password' , youtubePassword ) ;
}
if ( ! useDefaultDownloadingAgent && customDownloadingAgent ) {
downloadConfig . splice ( 0 , 0 , '--external-downloader' , customDownloadingAgent ) ;
}
let useYoutubeDLArchive = config _api . getConfigItem ( 'ytdl_use_youtubedl_archive' ) ;
if ( useYoutubeDLArchive ) {
const archive _path = path . join ( archivePath , ` archive_ ${ type } .txt ` ) ;
// create archive file if it doesn't exist
if ( ! fs . existsSync ( archive _path ) ) {
fs . closeSync ( fs . openSync ( archive _path , 'w' ) ) ;
}
let blacklist _path = path . join ( archivePath , ` blacklist_ ${ type } .txt ` ) ;
// create blacklist file if it doesn't exist
if ( ! fs . existsSync ( blacklist _path ) ) {
fs . closeSync ( fs . openSync ( blacklist _path , 'w' ) ) ;
}
let merged _path = fileFolderPath + 'merged.txt' ;
fs . ensureFileSync ( merged _path ) ;
// merges blacklist and regular archive
let inputPathList = [ archive _path , blacklist _path ] ;
let status = await mergeFiles ( inputPathList , merged _path ) ;
options . merged _string = fs . readFileSync ( merged _path , "utf8" ) ;
downloadConfig . push ( '--download-archive' , merged _path ) ;
}
if ( globalArgs && globalArgs !== '' ) {
// adds global args
downloadConfig = downloadConfig . concat ( globalArgs . split ( ' ' ) ) ;
}
}
resolve ( downloadConfig ) ;
} ) ;
}
// currently only works for single urls
async function getUrlInfos ( urls ) {
let startDate = Date . now ( ) ;
@ -1116,6 +1460,57 @@ function writeToBlacklist(type, line) {
fs . appendFileSync ( blacklistPath , line ) ;
}
// download management functions
function updateDownloads ( ) {
db . assign ( { downloads : downloads } ) . write ( ) ;
}
/ *
function checkDownloads ( ) {
for ( let [ session _id , session _downloads ] of Object . entries ( downloads ) ) {
for ( let [ download _uid , download _obj ] of Object . entries ( session _downloads ) ) {
if ( download _obj && ! download _obj [ 'complete' ] && ! download _obj [ 'error' ]
&& download _obj . timestamp _start > timestamp _server _start ) {
// download is still running (presumably)
download _obj . percent _complete = getDownloadPercent ( download _obj ) ;
}
}
}
}
* /
function getDownloadPercent ( download _obj ) {
if ( ! download _obj . final _size ) {
if ( fs . existsSync ( download _obj . expected _json _path ) ) {
const file _json = JSON . parse ( fs . readFileSync ( download _obj . expected _json _path , 'utf8' ) ) ;
let calculated _filesize = null ;
if ( file _json [ 'format_id' ] ) {
calculated _filesize = 0 ;
const formats _used = file _json [ 'format_id' ] . split ( '+' ) ;
for ( let i = 0 ; i < file _json [ 'formats' ] . length ; i ++ ) {
if ( formats _used . includes ( file _json [ 'formats' ] [ i ] [ 'format_id' ] ) ) {
calculated _filesize += file _json [ 'formats' ] [ i ] [ 'filesize' ] ;
}
}
}
download _obj . final _size = calculated _filesize ;
} else {
console . log ( 'could not find json file' ) ;
}
}
if ( fs . existsSync ( download _obj . expected _path ) ) {
const stats = fs . statSync ( download _obj . expected _path ) ;
const size = stats . size ;
return ( size / download _obj . final _size ) * 100 ;
} else {
console . log ( 'could not find file' ) ;
return 0 ;
}
}
// youtube-dl functions
async function startYoutubeDL ( ) {
// auto update youtube-dl
await autoUpdateYoutubeDL ( ) ;
@ -1287,297 +1682,55 @@ app.get('/api/using-encryption', function(req, res) {
app . post ( '/api/tomp3' , async function ( req , res ) {
var url = req . body . url ;
var date = Date . now ( ) ;
var audiopath = '%(title)s' ;
var customQualityConfiguration = req . body . customQualityConfiguration ;
var maxBitrate = req . body . maxBitrate ;
var globalArgs = config _api . getConfigItem ( 'ytdl_custom_args' ) ;
var customArgs = req . body . customArgs ;
var customOutput = req . body . customOutput ;
var youtubeUsername = req . body . youtubeUsername ;
var youtubePassword = req . body . youtubePassword ;
let downloadConfig = null ;
let qualityPath = '-f bestaudio' ;
let merged _path = null ;
let merged _string = null ;
var options = {
customArgs : req . body . customArgs ,
customOutput : req . body . customOutput ,
maxBitrate : req . body . maxBitrate ,
customQualityConfiguration : req . body . customQualityConfiguration ,
youtubeUsername : req . body . youtubeUsername ,
youtubePassword : req . body . youtubePassword ,
ui _uid : req . body . ui _uid
}
if ( customArgs ) {
downloadConfig = customArgs . split ( ' ' ) ;
const is _playlist = url . includes ( 'playlist' ) ;
if ( true || is _playlist )
result _obj = await downloadFileByURL _exec ( url , 'audio' , options , req . query . sessionID ) ;
else
result _obj = await downloadFileByURL _normal ( url , 'audio' , options , req . query . sessionID ) ;
if ( result _obj ) {
res . send ( result _obj ) ;
} else {
if ( customQualityConfiguration ) {
qualityPath = ` -f ${ customQualityConfiguration } ` ;
} else if ( maxBitrate ) {
if ( ! maxBitrate || maxBitrate === '' ) maxBitrate = '0' ;
qualityPath = ` --audio-quality ${ maxBitrate } `
}
if ( customOutput ) {
downloadConfig = [ '-x' , '--audio-format' , 'mp3' , '-o' , audioFolderPath + customOutput + '.%(ext)s' , '--write-info-json' , '--print-json' ] ;
} else {
downloadConfig = [ '-x' , '--audio-format' , 'mp3' , '-o' , audioFolderPath + audiopath + ".%(ext)s" , '--write-info-json' , '--print-json' ] ;
}
if ( youtubeUsername && youtubePassword ) {
downloadConfig . push ( '--username' , youtubeUsername , '--password' , youtubePassword ) ;
}
if ( qualityPath !== '' ) {
downloadConfig . splice ( 3 , 0 , qualityPath ) ;
}
if ( ! useDefaultDownloadingAgent && customDownloadingAgent ) {
downloadConfig . splice ( 0 , 0 , '--external-downloader' , customDownloadingAgent ) ;
}
let useYoutubeDLArchive = config _api . getConfigItem ( 'ytdl_use_youtubedl_archive' ) ;
if ( useYoutubeDLArchive ) {
const archive _path = path . join ( archivePath , 'archive_audio.txt' ) ;
// create archive file if it doesn't exist
if ( ! fs . existsSync ( archive _path ) ) {
fs . closeSync ( fs . openSync ( archive _path , 'w' ) ) ;
}
let blacklist _path = path . join ( archivePath , 'blacklist_audio.txt' ) ;
// create blacklist file if it doesn't exist
if ( ! fs . existsSync ( blacklist _path ) ) {
fs . closeSync ( fs . openSync ( blacklist _path , 'w' ) ) ;
}
// creates merged folder
merged _path = audioFolderPath + ` merged_ ${ uuid ( ) } .txt ` ;
// merges blacklist and regular archive
let inputPathList = [ archive _path , blacklist _path ] ;
let status = await mergeFiles ( inputPathList , merged _path ) ;
merged _string = fs . readFileSync ( merged _path , "utf8" ) ;
downloadConfig . push ( '--download-archive' , merged _path ) ;
}
if ( globalArgs && globalArgs !== '' ) {
// adds global args
downloadConfig = downloadConfig . concat ( globalArgs . split ( ' ' ) ) ;
}
res . sendStatus ( 500 ) ;
}
youtubedl . exec ( url , downloadConfig , { } , function ( err , output ) {
var uid = null ;
let new _date = Date . now ( ) ;
let difference = ( new _date - date ) / 1000 ;
logger . debug ( ` Audio download delay: ${ difference } seconds. ` ) ;
if ( err ) {
audiopath = "-1" ;
logger . error ( err . stderr ) ;
res . sendStatus ( 500 ) ;
throw err ;
} else if ( output ) {
var file _names = [ ] ;
if ( output . length === 0 || output [ 0 ] . length === 0 ) {
res . sendStatus ( 500 ) ;
return ;
}
for ( let i = 0 ; i < output . length ; i ++ ) {
let output _json = null ;
try {
output _json = JSON . parse ( output [ i ] ) ;
} catch ( e ) {
output _json = null ;
}
if ( ! output _json ) {
// if invalid, continue onto the next
continue ;
}
const filepath _no _extension = removeFileExtension ( output _json [ '_filename' ] ) ;
var full _file _path = filepath _no _extension + '.mp3' ;
var file _name = filepath _no _extension . substring ( audioFolderPath . length , filepath _no _extension . length ) ;
if ( fs . existsSync ( full _file _path ) ) {
let tags = {
title : output _json [ 'title' ] ,
artist : output _json [ 'artist' ] ? output _json [ 'artist' ] : output _json [ 'uploader' ]
}
// NodeID3.create(tags, function(frame) { })
let success = NodeID3 . write ( tags , full _file _path ) ;
if ( ! success ) logger . error ( 'Failed to apply ID3 tag to audio file ' + full _file _path ) ;
// registers file in DB
uid = registerFileDB ( full _file _path . substring ( audioFolderPath . length , full _file _path . length ) , 'audio' ) ;
} else {
logger . error ( 'Download failed: Output mp3 does not exist' ) ;
}
if ( file _name ) file _names . push ( file _name ) ;
}
let is _playlist = file _names . length > 1 ;
if ( merged _string !== null ) {
let current _merged _archive = fs . readFileSync ( merged _path , 'utf8' ) ;
let diff = current _merged _archive . replace ( merged _string , '' ) ;
const archive _path = path . join ( archivePath , 'archive_audio.txt' ) ;
fs . appendFileSync ( archive _path , diff ) ;
fs . unlinkSync ( merged _path )
}
var audiopathEncoded = encodeURIComponent ( file _names [ 0 ] ) ;
res . send ( {
audiopathEncoded : audiopathEncoded ,
file _names : is _playlist ? file _names : null ,
uid : uid
} ) ;
}
} ) ;
res . end ( "yes" ) ;
} ) ;
app . post ( '/api/tomp4' , async function ( req , res ) {
var url = req . body . url ;
var date = Date . now ( ) ;
var videopath = '%(title)s' ;
var globalArgs = config _api . getConfigItem ( 'ytdl_custom_args' ) ;
var customArgs = req . body . customArgs ;
var customOutput = req . body . customOutput ;
var selectedHeight = req . body . selectedHeight ;
var customQualityConfiguration = req . body . customQualityConfiguration ;
var youtubeUsername = req . body . youtubeUsername ;
var youtubePassword = req . body . youtubePassword ;
let merged _string = null ;
let downloadConfig = null ;
let qualityPath = 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4' ;
if ( url . includes ( 'tiktok' ) || url . includes ( 'pscp.tv' ) ) {
// tiktok videos fail when using the default format
qualityPath = 'best' ;
var options = {
customArgs : req . body . customArgs ,
customOutput : req . body . customOutput ,
selectedHeight : req . body . selectedHeight ,
customQualityConfiguration : req . body . customQualityConfiguration ,
youtubeUsername : req . body . youtubeUsername ,
youtubePassword : req . body . youtubePassword ,
ui _uid : req . body . ui _uid
}
if ( customArgs ) {
downloadConfig = customArgs . split ( ' ' ) ;
} else {
if ( customQualityConfiguration ) {
qualityPath = customQualityConfiguration ;
} else if ( selectedHeight && selectedHeight !== '' ) {
qualityPath = ` bestvideo[height= ${ selectedHeight } ]+bestaudio/best[height= ${ selectedHeight } ] ` ;
}
if ( customOutput ) {
downloadConfig = [ '-o' , videoFolderPath + customOutput + ".mp4" , '-f' , qualityPath , '--write-info-json' , '--print-json' ] ;
} else {
downloadConfig = [ '-o' , videoFolderPath + videopath + ".mp4" , '-f' , qualityPath , '--write-info-json' , '--print-json' ] ;
}
if ( youtubeUsername && youtubePassword ) {
downloadConfig . push ( '--username' , youtubeUsername , '--password' , youtubePassword ) ;
}
if ( ! useDefaultDownloadingAgent && customDownloadingAgent ) {
downloadConfig . splice ( 0 , 0 , '--external-downloader' , customDownloadingAgent ) ;
}
let useYoutubeDLArchive = config _api . getConfigItem ( 'ytdl_use_youtubedl_archive' ) ;
if ( useYoutubeDLArchive ) {
const archive _path = path . join ( archivePath , 'archive_video.txt' ) ;
// create archive file if it doesn't exist
if ( ! fs . existsSync ( archive _path ) ) {
fs . closeSync ( fs . openSync ( archive _path , 'w' ) ) ;
}
let blacklist _path = path . join ( archivePath , 'blacklist_video.txt' ) ;
// create blacklist file if it doesn't exist
if ( ! fs . existsSync ( blacklist _path ) ) {
fs . closeSync ( fs . openSync ( blacklist _path , 'w' ) ) ;
}
let merged _path = videoFolderPath + 'merged.txt' ;
// merges blacklist and regular archive
let inputPathList = [ archive _path , blacklist _path ] ;
let status = await mergeFiles ( inputPathList , merged _path ) ;
merged _string = fs . readFileSync ( merged _path , "utf8" ) ;
downloadConfig . push ( '--download-archive' , merged _path ) ;
}
if ( globalArgs && globalArgs !== '' ) {
// adds global args
downloadConfig = downloadConfig . concat ( globalArgs . split ( ' ' ) ) ;
}
const is _playlist = url . includes ( 'playlist' ) ;
let result _obj = null ;
if ( is _playlist )
result _obj = await downloadFileByURL _exec ( url , 'video' , options , req . query . sessionID ) ;
else
result _obj = await downloadFileByURL _normal ( url , 'video' , options , req . query . sessionID ) ;
if ( result _obj ) {
res . send ( result _obj ) ;
} else {
res . sendStatus ( 500 ) ;
}
youtubedl . exec ( url , downloadConfig , { } , function ( err , output ) {
var uid = null ;
let new _date = Date . now ( ) ;
let difference = ( new _date - date ) / 1000 ;
logger . debug ( ` Video download delay: ${ difference } seconds. ` ) ;
if ( err ) {
videopath = "-1" ;
logger . error ( err . stderr ) ;
res . sendStatus ( 500 ) ;
throw err ;
} else if ( output ) {
if ( output . length === 0 || output [ 0 ] . length === 0 ) {
res . sendStatus ( 500 ) ;
return ;
}
var file _names = [ ] ;
for ( let i = 0 ; i < output . length ; i ++ ) {
let output _json = null ;
try {
output _json = JSON . parse ( output [ i ] ) ;
} catch ( e ) {
output _json = null ;
}
var modified _file _name = output _json ? output _json [ 'title' ] : null ;
if ( ! output _json ) {
continue ;
}
// get filepath with no extension
const filepath _no _extension = removeFileExtension ( output _json [ '_filename' ] ) ;
var full _file _path = filepath _no _extension + '.mp4' ;
var file _name = filepath _no _extension . substring ( audioFolderPath . length , filepath _no _extension . length ) ;
// renames file if necessary due to bug
if ( ! fs . existsSync ( output _json [ '_filename' ] && fs . existsSync ( output _json [ '_filename' ] + '.webm' ) ) ) {
try {
fs . renameSync ( output _json [ '_filename' ] + '.webm' , output _json [ '_filename' ] ) ;
logger . info ( 'Renamed ' + file _name + '.webm to ' + file _name ) ;
} catch ( e ) {
}
}
// registers file in DB
uid = registerFileDB ( full _file _path . substring ( videoFolderPath . length , full _file _path . length ) , 'video' ) ;
if ( file _name ) file _names . push ( file _name ) ;
}
let is _playlist = file _names . length > 1 ;
if ( ! is _playlist ) audiopath = file _names [ 0 ] ;
if ( merged _string !== null ) {
let current _merged _archive = fs . readFileSync ( videoFolderPath + 'merged.txt' , 'utf8' ) ;
let diff = current _merged _archive . replace ( merged _string , '' ) ;
const archive _path = path . join ( archivePath , 'archive_video.txt' ) ;
fs . appendFileSync ( archive _path , diff ) ;
}
var videopathEncoded = encodeURIComponent ( file _names [ 0 ] ) ;
res . send ( {
videopathEncoded : videopathEncoded ,
file _names : is _playlist ? file _names : null ,
uid : uid
} ) ;
res . end ( "yes" ) ;
}
} ) ;
res . end ( "yes" ) ;
} ) ;
// gets the status of the mp3 file that's being downloaded
@ -2282,6 +2435,74 @@ app.get('/api/audio/:id', function(req , res){
}
} ) ;
// Downloads management
app . get ( '/api/downloads' , async ( req , res ) => {
/ *
if ( ! last _downloads _check || Date . now ( ) - last _downloads _check > downloads _check _interval ) {
last _downloads _check = Date . now ( ) ;
updateDownloads ( ) ;
}
* /
res . send ( { downloads : downloads } ) ;
} ) ;
app . post ( '/api/download' , async ( req , res ) => {
var session _id = req . body . session _id ;
var download _id = req . body . download _id ;
let found _download = null ;
// find download
if ( downloads [ session _id ] && Object . keys ( downloads [ session _id ] ) ) {
let session _downloads = Object . values ( downloads [ session _id ] ) ;
for ( let i = 0 ; i < session _downloads . length ; i ++ ) {
let session _download = session _downloads [ i ] ;
if ( session _download && session _download [ 'ui_uid' ] === download _id ) {
found _download = session _download ;
break ;
}
}
}
if ( found _download ) {
res . send ( { download : found _download } ) ;
} else {
res . send ( { download : null } ) ;
}
} ) ;
app . post ( '/api/clearDownloads' , async ( req , res ) => {
let success = false ;
var delete _all = req . body . delete _all ;
if ( ! req . body . session _id ) req . body . session _id = 'undeclared' ;
var session _id = req . body . session _id ;
var download _id = req . body . download _id ;
if ( delete _all ) {
// delete all downloads
downloads = { } ;
success = true ;
} else if ( download _id ) {
// delete just 1 download
if ( downloads [ session _id ] [ download _id ] ) {
delete downloads [ session _id ] [ download _id ] ;
success = true ;
} else if ( ! downloads [ session _id ] ) {
logger . error ( ` Session ${ session _id } has no downloads. ` )
} else if ( ! downloads [ session _id ] [ download _id ] ) {
logger . error ( ` Download ' ${ download _id } ' for session ' ${ session _id } ' could not be found ` ) ;
}
} else if ( session _id ) {
// delete a session's downloads
if ( downloads [ session _id ] ) {
delete downloads [ session _id ] ;
success = true ;
} else {
logger . error ( ` Session ${ session _id } has no downloads. ` )
}
}
updateDownloads ( ) ;
res . send ( { success : success , downloads : downloads } ) ;
} ) ;
app . post ( '/api/getVideoInfos' , async ( req , res ) => {
let fileNames = req . body . fileNames ;