diff --git a/backend/downloader.js b/backend/downloader.js index 2166ef4..a7128f5 100644 --- a/backend/downloader.js +++ b/backend/downloader.js @@ -567,15 +567,71 @@ exports.generateArgs = async (url, type, options, user_uid = null, simulated = f return downloadConfig; } +function filterInfoLookupArgs(args = []) { + if (!Array.isArray(args)) return []; + + // Keep selection/output/auth args so predicted filename and size match the real download, + // but remove flags that write files or trigger an actual download during info lookup. + const args_to_remove = new Set([ + '--write-info-json', + '--write-thumbnail', + '--write-all-thumbnails', + '--write-description', + '--write-comments', + '--write-annotations', + '--write-subs', + '--write-auto-subs', + '--all-subs', + '--print-json', + '-j', + '--dump-json', + '-J', + '--dump-single-json', + '--no-clean-info-json', + '--no-simulate', + '--force-write-archive' + ]); + const args_with_values_to_remove = new Set([ + '--download-archive', + '--exec', + '--exec-before-download', + '--print' + ]); + + const filtered_args = []; + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (args_to_remove.has(arg)) continue; + + if (arg === '--print-to-file') { + // --print-to-file takes a template and destination path. + i += 2; + continue; + } + + if (args_with_values_to_remove.has(arg)) { + i++; + continue; + } + + if (typeof arg === 'string' && arg.startsWith('--print=')) continue; + + filtered_args.push(arg); + } + + return filtered_args; +} + exports.getVideoInfoByURL = async (url, args = [], download_uid = null) => { logger.info('[DEBUG] getVideoInfoByURL called'); - // For info retrieval, completely ignore input args and build clean minimal set - // This prevents file-writing operations that cause yt-dlp to hang - const new_args = ['--dump-json']; + // Preserve safe args (notably -o/-f/-S) so progress prediction uses the real + // filename and selected formats, but strip flags that write files/download data. + const new_args = filterInfoLookupArgs(args); + new_args.push('--dump-json'); - // Only add cookies if they exist - if (await fs.pathExists(path.join(__dirname, 'appdata', 'cookies.txt'))) { + // Only add cookies if they exist and args do not already provide them. + if (new_args.indexOf('--cookies') === -1 && await fs.pathExists(path.join(__dirname, 'appdata', 'cookies.txt'))) { new_args.push('--cookies', path.join('appdata', 'cookies.txt')); } diff --git a/backend/test/tests.js b/backend/test/tests.js index 0accdd6..3ad0c0a 100644 --- a/backend/test/tests.js +++ b/backend/test/tests.js @@ -521,6 +521,41 @@ describe('Downloader', function() { assert(!!info && info.length > 0); }); + it('Get file info preserves safe args used for progress prediction', async function() { + let captured_args = null; + const original_runYoutubeDL = youtubedl_api.runYoutubeDL; + youtubedl_api.runYoutubeDL = async (requestedUrl, run_args) => { + captured_args = run_args; + return { + callback: Promise.resolve({parsed_output: fixture_single, err: null}) + }; + }; + + try { + await _originalGetVideoInfoByURL(url, [ + '-o', '/tmp/%(title)s.%(ext)s', + '-f', 'bestvideo+bestaudio', + '--write-info-json', + '--no-clean-info-json', + '-j', + '--no-simulate' + ]); + } finally { + youtubedl_api.runYoutubeDL = original_runYoutubeDL; + } + + assert(captured_args); + assert(captured_args.includes('-o')); + assert(captured_args.includes('/tmp/%(title)s.%(ext)s')); + assert(captured_args.includes('-f')); + assert(captured_args.includes('bestvideo+bestaudio')); + assert(captured_args.includes('--dump-json')); + assert(!captured_args.includes('--write-info-json')); + assert(!captured_args.includes('--no-clean-info-json')); + assert(!captured_args.includes('-j')); + assert(!captured_args.includes('--no-simulate')); + }); + it('Download file', async function() { this.timeout(300000); await downloader_api.setupDownloads();