diff --git a/backend/app.js b/backend/app.js index 9058e51..38ab93d 100644 --- a/backend/app.js +++ b/backend/app.js @@ -7,7 +7,7 @@ var path = require('path'); var youtubedl = require('youtube-dl'); var ffmpeg = require('fluent-ffmpeg'); var compression = require('compression'); -var https = require('https'); +var glob = require("glob") var multer = require('multer'); var express = require("express"); var bodyParser = require("body-parser"); @@ -1146,12 +1146,29 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { type: type, percent_complete: 0, is_playlist: url.includes('playlist'), - timestamp_start: Date.now() + timestamp_start: Date.now(), + filesize: null }; const download = downloads[session][download_uid]; updateDownloads(); + // get video info prior to download + const info = await getVideoInfoByURL(url, downloadConfig, download); + if (!info) { + resolve(false); + return; + } else { + // store info in download for future use + download['_filename'] = info['_filename']; + download['filesize'] = utils.getExpectedFileSize(info); + } + + const download_checker = setInterval(() => checkDownloadPercent(download), 1000); + + // download file youtubedl.exec(url, downloadConfig, {}, function(err, output) { + clearInterval(download_checker); // stops the download checker from running as the download finished (or errored) + download['downloading'] = false; download['timestamp_end'] = Date.now(); var file_uid = null; @@ -1164,7 +1181,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) { download['error'] = err.stderr; updateDownloads(); resolve(false); - throw err; + return; } else if (output) { if (output.length === 0 || output[0].length === 0) { download['error'] = 'No output. Check if video already exists in your archive.'; @@ -1407,7 +1424,7 @@ async function generateArgs(url, type, options) { var youtubePassword = options.youtubePassword; let downloadConfig = null; - let qualityPath = (is_audio && !options.skip_audio_args) ? ['-f', 'bestaudio'] : ['-f', 'best[ext=mp4]']; + let qualityPath = (is_audio && !options.skip_audio_args) ? ['-f', 'bestaudio'] : ['-f', 'bestvideo+bestaudio', '--merge-output-format', 'mp4']; const is_youtube = url.includes('youtu'); if (!is_audio && !is_youtube) { // tiktok videos fail when using the default format @@ -1485,6 +1502,10 @@ async function generateArgs(url, type, options) { downloadConfig.push('--download-archive', merged_path); } + if (config_api.getConfigItem('ytdl_include_thumbnail')) { + downloadConfig.push('--write-thumbnail'); + } + if (globalArgs && globalArgs !== '') { // adds global args if (downloadConfig.indexOf('-o') !== -1 && globalArgs.split(',,').indexOf('-o') !== -1) { @@ -1497,11 +1518,36 @@ async function generateArgs(url, type, options) { } logger.verbose(`youtube-dl args being used: ${downloadConfig.join(',')}`); - // downloadConfig.map((arg) => `"${arg}"`); resolve(downloadConfig); }); } +async function getVideoInfoByURL(url, args = [], download = null) { + return new Promise(resolve => { + // remove bad args + const new_args = [...args]; + + const archiveArgIndex = new_args.indexOf('--download-archive'); + if (archiveArgIndex !== -1) { + new_args.splice(archiveArgIndex, 2); + } + + // actually get info + youtubedl.getInfo(url, new_args, (err, output) => { + if (output) { + resolve(output); + } else { + logger.error(`Error while retrieving info on video with URL ${url} with the following message: ${err}`); + if (download) { + download['error'] = `Failed pre-check for video info: ${err}`; + updateDownloads(); + } + resolve(null); + } + }); + }); +} + // currently only works for single urls async function getUrlInfos(urls) { let startDate = Date.now(); @@ -1559,47 +1605,33 @@ 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']; - } +function checkDownloadPercent(download) { + /* + This is more of an art than a science, we're just selecting files that start with the file name, + thus capturing the parts being downloaded in files named like so: '