diff --git a/backend/app.js b/backend/app.js index 33363d9..97a29df 100644 --- a/backend/app.js +++ b/backend/app.js @@ -987,8 +987,9 @@ app.post('/api/downloadFile', optionalJwt, async function(req, res) { const url = req.body.url; const type = req.body.type; const user_uid = req.isAuthenticated() ? req.user.uid : null; - var options = { + const options = { customArgs: req.body.customArgs, + additionalArgs: req.body.additionalArgs, customOutput: req.body.customOutput, selectedHeight: req.body.selectedHeight, customQualityConfiguration: req.body.customQualityConfiguration, @@ -996,7 +997,7 @@ app.post('/api/downloadFile', optionalJwt, async function(req, res) { youtubePassword: req.body.youtubePassword, ui_uid: req.body.ui_uid, cropFileSettings: req.body.cropFileSettings - } + }; const download = await downloader_api.createDownload(url, type, options, user_uid); @@ -1012,6 +1013,26 @@ app.post('/api/killAllDownloads', optionalJwt, async function(req, res) { res.send(result_obj); }); +app.post('/api/generateArgs', optionalJwt, async function(req, res) { + const url = req.body.url; + const type = req.body.type; + const user_uid = req.isAuthenticated() ? req.user.uid : null; + const options = { + customArgs: req.body.customArgs, + additionalArgs: req.body.additionalArgs, + 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, + cropFileSettings: req.body.cropFileSettings + }; + + const args = await downloader_api.generateArgs(url, type, options, user_uid, true); + res.send({args: args}); +}); + // gets all download mp3s app.get('/api/getMp3s', optionalJwt, async function(req, res) { // TODO: simplify diff --git a/backend/downloader.js b/backend/downloader.js index 16aaaa8..1c428ea 100644 --- a/backend/downloader.js +++ b/backend/downloader.js @@ -369,7 +369,7 @@ async function downloadQueuedFile(download_uid) { // helper functions -exports.generateArgs = async (url, type, options, user_uid = null) => { +exports.generateArgs = async (url, type, options, user_uid = null, simulated = false) => { const audioFolderPath = config_api.getConfigItem('ytdl_audio_folder_path'); const videoFolderPath = config_api.getConfigItem('ytdl_video_folder_path'); @@ -510,7 +510,7 @@ exports.generateArgs = async (url, type, options, user_uid = null) => { // filter out incompatible args downloadConfig = filterArgs(downloadConfig, is_audio); - logger.verbose(`youtube-dl args being used: ${downloadConfig.join(',')}`); + if (!simulated) logger.verbose(`youtube-dl args being used: ${downloadConfig.join(',')}`); return downloadConfig; } diff --git a/src/app/main/main.component.css b/src/app/main/main.component.css index e2325f2..5a712ae 100644 --- a/src/app/main/main.component.css +++ b/src/app/main/main.component.css @@ -129,7 +129,9 @@ mat-form-field.mat-form-field { } .edit-button { - margin-left: 10px; + margin-left: 5px; + margin-top: -6px; + margin-bottom: -5px; top: -5px; } diff --git a/src/app/main/main.component.html b/src/app/main/main.component.html index 3bfdc23..cba6565 100644 --- a/src/app/main/main.component.html +++ b/src/app/main/main.component.html @@ -111,8 +111,13 @@ + + + Replace args + + - + No need to include URL, just everything after. Args are delimited using two commas like so: ,, @@ -127,7 +132,7 @@ - + Documentation. Path is relative to the config download path. Don't include extension. @@ -140,13 +145,13 @@ Use authentication - - + +
- - + +
@@ -155,13 +160,13 @@ Crop file - - + +
- - + +
diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index 996d819..37653ba 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit, ElementRef, ViewChild, ViewChildren, QueryList } from '@angular/core'; import {PostsService} from '../posts.services'; import {FileCardComponent} from '../file-card/file-card.component'; -import { Observable } from 'rxjs'; +import { Observable, Subject } from 'rxjs'; import {FormControl, Validators} from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; @@ -9,7 +9,6 @@ import { saveAs } from 'file-saver'; import { YoutubeSearchService, Result } from '../youtube-search.service'; import { Router, ActivatedRoute } from '@angular/router'; import { Platform } from '@angular/cdk/platform'; -import { v4 as uuid } from 'uuid'; import { ArgModifierDialogComponent } from 'app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component'; import { RecentVideosComponent } from 'app/components/recent-videos/recent-videos.component'; @@ -50,6 +49,7 @@ export class MainComponent implements OnInit { customArgsEnabled = false; customArgs = null; customOutputEnabled = false; + replaceArgs = false; customOutput = null; youtubeAuthEnabled = false; youtubeUsername = null; @@ -212,6 +212,7 @@ export class MainComponent implements OnInit { error: false }; + argsChangedSubject: Subject = new Subject(false); simulatedOutput = ''; constructor(public postsService: PostsService, private youtubeSearch: YoutubeSearchService, public snackBar: MatSnackBar, @@ -224,8 +225,6 @@ export class MainComponent implements OnInit { if (this.autoStartDownload) { this.downloadClicked(); } - - setInterval(() => this.getSimulatedOutput(), 1000); } async loadConfig() { @@ -261,6 +260,10 @@ export class MainComponent implements OnInit { this.customOutputEnabled = localStorage.getItem('customOutputEnabled') === 'true'; } + if (localStorage.getItem('replaceArgs') !== null) { + this.replaceArgs = localStorage.getItem('replaceArgs') === 'true'; + } + if (localStorage.getItem('youtubeAuthEnabled') !== null) { this.youtubeAuthEnabled = localStorage.getItem('youtubeAuthEnabled') === 'true'; } @@ -323,6 +326,12 @@ export class MainComponent implements OnInit { this.autoStartDownload = true; } + this.argsChangedSubject + .debounceTime(500) + .subscribe((should_simulate) => { + if (should_simulate) this.getSimulatedOutput(); + }); + this.setCols(); } @@ -412,7 +421,8 @@ export class MainComponent implements OnInit { this.urlError = false; // get common args - const customArgs = (this.customArgsEnabled ? this.customArgs : null); + const customArgs = (this.customArgsEnabled && this.replaceArgs ? this.customArgs : null); + const additionalArgs = (this.customArgsEnabled && !this.replaceArgs ? this.customArgs : null); const customOutput = (this.customOutputEnabled ? this.customOutput : null); const youtubeUsername = (this.youtubeAuthEnabled && this.youtubeUsername ? this.youtubeUsername : null); const youtubePassword = (this.youtubeAuthEnabled && this.youtubePassword ? this.youtubePassword : null); @@ -445,7 +455,7 @@ export class MainComponent implements OnInit { this.downloadingfile = true; this.postsService.downloadFile(this.url, type, (this.selectedQuality === '' ? null : this.selectedQuality), - customQualityConfiguration, customArgs, customOutput, youtubeUsername, youtubePassword, cropFileSettings).subscribe(res => { + customQualityConfiguration, customArgs, additionalArgs, customOutput, youtubeUsername, youtubePassword, cropFileSettings).subscribe(res => { this.current_download = res['download']; this.downloads.push(res['download']); this.download_uids.push(res['download']['uid']); @@ -593,6 +603,7 @@ export class MainComponent implements OnInit { if (str !== this.last_valid_url && this.allowQualitySelect) { // get info this.getURLInfo(str); + this.argsChangedSubject.next(true); } this.last_valid_url = str; } @@ -630,79 +641,44 @@ export class MainComponent implements OnInit { } } - getSimulatedOutput() { - const customArgsExists = this.customArgsEnabled && this.customArgs; - const globalArgsExists = this.globalCustomArgs && this.globalCustomArgs !== ''; - - let full_string_array: string[] = []; - const base_string_array = ['youtube-dl', this.url]; - - if (customArgsExists) { - this.simulatedOutput = base_string_array.join(' ') + ' ' + this.customArgs.split(',,').join(' '); - return this.simulatedOutput; - } - - full_string_array.push(...base_string_array); + argChanged(): void { + this.argsChangedSubject.next(true); + } - const base_path = this.audioOnly ? this.audioFolderPath : this.videoFolderPath; - const ext = this.audioOnly ? '.mp3' : '.mp4'; - // gets output - let output_string_array = ['-o', base_path + '%(title)s' + ext]; - if (this.customOutputEnabled && this.customOutput) { - output_string_array = ['-o', base_path + this.customOutput + ext]; - } - // before pushing output, should check if using an external downloader - if (!this.useDefaultDownloadingAgent && this.customDownloadingAgent === 'aria2c') { - full_string_array.push('--external-downloader', 'aria2c'); - } - // pushes output - full_string_array.push(...output_string_array); + getSimulatedOutput(): void { + // this function should be very similar to downloadFile() + const customArgs = (this.customArgsEnabled && this.replaceArgs ? this.customArgs : null); + const additionalArgs = (this.customArgsEnabled && !this.replaceArgs ? this.customArgs : null); + const customOutput = (this.customOutputEnabled ? this.customOutput : null); + const youtubeUsername = (this.youtubeAuthEnabled && this.youtubeUsername ? this.youtubeUsername : null); + const youtubePassword = (this.youtubeAuthEnabled && this.youtubePassword ? this.youtubePassword : null); - // logic splits into audio and video modes - if (this.audioOnly) { - // adds base audio string - const format_array = []; - const audio_format = this.getSelectedAudioFormat(); - if (audio_format) { - format_array.push('-f', audio_format); - } else if (this.selectedQuality) { - format_array.push('--audio-quality', this.selectedQuality['format_id']); - } + const type = this.audioOnly ? 'audio' : 'video'; - // pushes formats - full_string_array.splice(2, 0, ...format_array); + const customQualityConfiguration = type === 'audio' ? this.getSelectedAudioFormat() : this.getSelectedVideoFormat(); - const additional_params = ['-x', '--audio-format', 'mp3', '--write-info-json', '--print-json']; + let cropFileSettings = null; - full_string_array.push(...additional_params); - } else { - // adds base video string - let format_array = ['-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4']; - const video_format = this.getSelectedVideoFormat(); - if (video_format) { - format_array = ['-f', video_format]; - } else if (this.selectedQuality) { - format_array = [`bestvideo[height=${this.selectedQuality['format_id']}]+bestaudio/best[height=${this.selectedQuality}]`]; + if (this.cropFile) { + cropFileSettings = { + cropFileStart: this.cropFileStart, + cropFileEnd: this.cropFileEnd } - - // pushes formats - full_string_array.splice(2, 0, ...format_array); - - const additional_params = ['--write-info-json', '--print-json']; - - full_string_array.push(...additional_params); - } - - if (this.use_youtubedl_archive) { - full_string_array.push('--download-archive', 'archive.txt'); } - if (globalArgsExists) { - full_string_array = full_string_array.concat(this.globalCustomArgs.split(',,')); - } - - this.simulatedOutput = full_string_array.join(' '); - return this.simulatedOutput; + this.postsService.generateArgs(this.url, type, (this.selectedQuality === '' ? null : this.selectedQuality), + customQualityConfiguration, customArgs, additionalArgs, customOutput, youtubeUsername, youtubePassword, cropFileSettings).subscribe(res => { + const simulated_args = res['args']; + if (simulated_args) { + // hide password if needed + const passwordIndex = simulated_args.indexOf('--password'); + console.log(passwordIndex); + if (passwordIndex !== -1 && passwordIndex !== simulated_args.length - 1) { + simulated_args[passwordIndex + 1] = simulated_args[passwordIndex + 1].replace(/./g, '*'); + } + this.simulatedOutput = `youtube-dl ${this.url} ${simulated_args.join(' ')}`; + } + }); } errorFormats(url) { @@ -746,6 +722,7 @@ export class MainComponent implements OnInit { videoModeChanged(new_val) { this.selectedQuality = ''; localStorage.setItem('audioOnly', new_val.checked.toString()); + this.argsChangedSubject.next(true); } autoplayChanged(new_val) { @@ -754,29 +731,22 @@ export class MainComponent implements OnInit { customArgsEnabledChanged(new_val) { localStorage.setItem('customArgsEnabled', new_val.checked.toString()); - if (new_val.checked === true && this.customOutputEnabled) { - this.customOutputEnabled = false; - localStorage.setItem('customOutputEnabled', 'false'); + this.argsChangedSubject.next(true); + } - this.youtubeAuthEnabled = false; - localStorage.setItem('youtubeAuthEnabled', 'false'); - } + replaceArgsChanged(new_val) { + localStorage.setItem('replaceArgs', new_val.checked.toString()); + this.argsChangedSubject.next(true); } customOutputEnabledChanged(new_val) { localStorage.setItem('customOutputEnabled', new_val.checked.toString()); - if (new_val.checked === true && this.customArgsEnabled) { - this.customArgsEnabled = false; - localStorage.setItem('customArgsEnabled', 'false'); - } + this.argsChangedSubject.next(true); } youtubeAuthEnabledChanged(new_val) { localStorage.setItem('youtubeAuthEnabled', new_val.checked.toString()); - if (new_val.checked === true && this.customArgsEnabled) { - this.customArgsEnabled = false; - localStorage.setItem('customArgsEnabled', 'false'); - } + this.argsChangedSubject.next(true); } getAudioAndVideoFormats(formats) { diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 22faa70..bee6500 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -175,11 +175,25 @@ export class PostsService implements CanActivate { } // tslint:disable-next-line: max-line-length - downloadFile(url: string, type: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, customOutput: string = null, youtubeUsername: string = null, youtubePassword: string = null, cropFileSettings = null) { + downloadFile(url: string, type: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, additionalArgs: string = null, customOutput: string = null, youtubeUsername: string = null, youtubePassword: string = null, cropFileSettings = null) { return this.http.post(this.path + 'downloadFile', {url: url, selectedHeight: selectedQuality, customQualityConfiguration: customQualityConfiguration, customArgs: customArgs, + additionalArgs: additionalArgs, + customOutput: customOutput, + youtubeUsername: youtubeUsername, + youtubePassword: youtubePassword, + type: type, + cropFileSettings: cropFileSettings}, this.httpOptions); + } + + generateArgs(url: string, type: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, additionalArgs: string = null, customOutput: string = null, youtubeUsername: string = null, youtubePassword: string = null, cropFileSettings = null) { + return this.http.post(this.path + 'generateArgs', {url: url, + selectedHeight: selectedQuality, + customQualityConfiguration: customQualityConfiguration, + customArgs: customArgs, + additionalArgs: additionalArgs, customOutput: customOutput, youtubeUsername: youtubeUsername, youtubePassword: youtubePassword,