Improved arg simulation -- now uses same method as the actual download

Added checkbox for advanced custom args to either replace all args or append
pull/226/head
Isaac Abadi 4 years ago
parent 8aa354ac24
commit 75fc09ed99

@ -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

@ -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;
}

@ -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;
}

@ -111,8 +111,13 @@
</ng-container>
</mat-checkbox>
<button class="edit-button" (click)="openArgsModifierDialog()" mat-icon-button><mat-icon>edit</mat-icon></button>
<mat-checkbox color="accent" [disabled]="!customArgsEnabled || current_download" (change)="replaceArgsChanged($event)" [(ngModel)]="replaceArgs" style="z-index: 999; margin-left: 10px" [ngModelOptions]="{standalone: true}">
<ng-container i18n="Replace args">
Replace args
</ng-container>
</mat-checkbox>
<mat-form-field color="accent" style="margin-bottom: 42px;" class="advanced-input">
<input [(ngModel)]="customArgs" [ngModelOptions]="{standalone: true}" [disabled]="!customArgsEnabled" matInput placeholder="Custom args" i18n-placeholder="Custom args placeholder">
<input [(ngModel)]="customArgs" [ngModelOptions]="{standalone: true}" [disabled]="!customArgsEnabled" matInput (ngModelChange)="argChanged()" placeholder="Custom args" i18n-placeholder="Custom args placeholder">
<mat-hint>
<ng-container i18n="Custom Args input hint">
No need to include URL, just everything after. Args are delimited using two commas like so: ,,
@ -127,7 +132,7 @@
</ng-container>
</mat-checkbox>
<mat-form-field style="margin-bottom: 42px;" color="accent" class="advanced-input">
<input [(ngModel)]="customOutput" [ngModelOptions]="{standalone: true}" [disabled]="!customOutputEnabled" matInput placeholder="Custom output" i18n-placeholder="Custom output placeholder">
<input [(ngModel)]="customOutput" [ngModelOptions]="{standalone: true}" [disabled]="!customOutputEnabled" matInput (ngModelChange)="argChanged()" placeholder="Custom output" i18n-placeholder="Custom output placeholder">
<mat-hint><a target="_blank" href="https://github.com/ytdl-org/youtube-dl/blob/master/README.md#output-template">
<ng-container i18n="Youtube-dl output template documentation link">Documentation</ng-container></a>.
<ng-container i18n="Custom Output input hint">Path is relative to the config download path. Don't include extension.</ng-container>
@ -140,13 +145,13 @@
Use authentication
</ng-container>
</mat-checkbox>
<mat-form-field color="accent" class="advanced-input">
<input [(ngModel)]="youtubeUsername" [ngModelOptions]="{standalone: true}" [disabled]="!youtubeAuthEnabled" matInput placeholder="Username" i18n-placeholder="YT Username placeholder">
<mat-form-field *ngIf="youtubeAuthEnabled" color="accent" class="advanced-input">
<input [(ngModel)]="youtubeUsername" [ngModelOptions]="{standalone: true}" matInput (ngModelChange)="argChanged()" placeholder="Username" i18n-placeholder="YT Username placeholder">
</mat-form-field>
</div>
<div *ngIf="!youtubeAuthDisabledOverride" class="col-12 col-sm-6 mt-3">
<mat-form-field style="margin-top: 31px;" color="accent" class="advanced-input">
<input [(ngModel)]="youtubePassword" type="password" [ngModelOptions]="{standalone: true}" [disabled]="!youtubeAuthEnabled" matInput placeholder="Password" i18n-placeholder="YT Password placeholder">
<mat-form-field *ngIf="youtubeAuthEnabled" style="margin-top: 31px;" color="accent" class="advanced-input">
<input [(ngModel)]="youtubePassword" type="password" [ngModelOptions]="{standalone: true}" matInput (ngModelChange)="argChanged()" placeholder="Password" i18n-placeholder="YT Password placeholder">
</mat-form-field>
</div>
<div class="col-12 col-sm-6 mt-3">
@ -155,13 +160,13 @@
Crop file
</ng-container>
</mat-checkbox>
<mat-form-field color="accent" class="advanced-input">
<input [(ngModel)]="cropFileStart" type="number" [ngModelOptions]="{standalone: true}" [disabled]="!cropFile" matInput placeholder="Crop from (seconds)" i18n-placeholder="Crop from placeholder">
<mat-form-field *ngIf="cropFile" color="accent" class="advanced-input">
<input [(ngModel)]="cropFileStart" type="number" [ngModelOptions]="{standalone: true}" matInput placeholder="Crop from (seconds)" i18n-placeholder="Crop from placeholder">
</mat-form-field>
</div>
<div class="col-12 col-sm-6 mt-3">
<mat-form-field style="margin-top: 31px;" color="accent" class="advanced-input">
<input [(ngModel)]="cropFileEnd" type="number" [ngModelOptions]="{standalone: true}" [disabled]="!cropFile" matInput placeholder="Crop to (seconds)" i18n-placeholder="Crop to placeholder">
<mat-form-field *ngIf="cropFile" style="margin-top: 31px;" color="accent" class="advanced-input">
<input [(ngModel)]="cropFileEnd" type="number" [ngModelOptions]="{standalone: true}" matInput placeholder="Crop to (seconds)" i18n-placeholder="Crop to placeholder">
</mat-form-field>
</div>
</div>

@ -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<boolean> = new Subject<boolean>(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) {

@ -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,

Loading…
Cancel
Save