added multiple download support

lazy loaded images now reload after a new download
This commit is contained in:
Isaac Grynsztein
2020-02-27 01:06:32 -05:00
parent b646db4828
commit bcd879ebc8
13 changed files with 192 additions and 15 deletions

View File

@@ -18,7 +18,8 @@
"title_top": "Youtube Downloader", "title_top": "Youtube Downloader",
"file_manager_enabled": true, "file_manager_enabled": true,
"allow_quality_select": true, "allow_quality_select": true,
"download_only_mode": false "download_only_mode": false,
"allow_multi_download_mode": true
}, },
"API": { "API": {
"use_youtube_API": false, "use_youtube_API": false,

View File

@@ -18,7 +18,8 @@
"title_top": "Youtube Downloader", "title_top": "Youtube Downloader",
"file_manager_enabled": true, "file_manager_enabled": true,
"allow_quality_select": true, "allow_quality_select": true,
"download_only_mode": false "download_only_mode": false,
"allow_multi_download_mode": true
}, },
"API": { "API": {
"use_youtube_API": false, "use_youtube_API": false,

View File

@@ -56,6 +56,11 @@ let CONFIG_ITEMS = {
'key': 'ytdl_download_only_mode', 'key': 'ytdl_download_only_mode',
'path': 'YoutubeDLMaterial.Extra.download_only_mode' 'path': 'YoutubeDLMaterial.Extra.download_only_mode'
}, },
'ytdl_allow_multi_download_mode': {
'key': 'ytdl_allow_multi_download_mode',
'path': 'YoutubeDLMaterial.Extra.allow_multi_download_mode'
},
// API // API
'ytdl_use_youtube_api': { 'ytdl_use_youtube_api': {

View File

@@ -17,6 +17,7 @@ services:
ytdl_file_manager_enabled: 'true' ytdl_file_manager_enabled: 'true'
ytdl_allow_quality_select: 'true' ytdl_allow_quality_select: 'true'
ytdl_download_only_mode: 'false' ytdl_download_only_mode: 'false'
ytdl_allow_multi_download_mode: true
ytdl_use_youtube_api: 'false' ytdl_use_youtube_api: 'false'
ytdl_youtube_api_key: 'false' ytdl_youtube_api_key: 'false'
ytdl_default_theme: default ytdl_default_theme: default

View File

@@ -25,11 +25,12 @@ import {VgBufferingModule} from 'videogular2/compiled/buffering';
import { InputDialogComponent } from './input-dialog/input-dialog.component'; import { InputDialogComponent } from './input-dialog/input-dialog.component';
import { LazyLoadImageModule, IsVisibleProps } from 'ng-lazyload-image'; import { LazyLoadImageModule, IsVisibleProps } from 'ng-lazyload-image';
import { NgxContentLoadingModule } from 'ngx-content-loading'; import { NgxContentLoadingModule } from 'ngx-content-loading';
import { audioFilesMouseHovering, videoFilesMouseHovering } from './main/main.component'; import { audioFilesMouseHovering, videoFilesMouseHovering, audioFilesOpened, videoFilesOpened } from './main/main.component';
import { CreatePlaylistComponent } from './create-playlist/create-playlist.component'; import { CreatePlaylistComponent } from './create-playlist/create-playlist.component';
import { DownloadItemComponent } from './download-item/download-item.component';
export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps<any>) { export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps<any>) {
return (element.id === 'video' ? videoFilesMouseHovering : audioFilesMouseHovering); return (element.id === 'video' ? videoFilesMouseHovering || videoFilesOpened : audioFilesMouseHovering || audioFilesOpened);
} }
@NgModule({ @NgModule({
@@ -39,7 +40,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
MainComponent, MainComponent,
PlayerComponent, PlayerComponent,
InputDialogComponent, InputDialogComponent,
CreatePlaylistComponent CreatePlaylistComponent,
DownloadItemComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,

View File

@@ -0,0 +1,16 @@
<div>
<mat-grid-list [rowHeight]="50" [cols]="24">
<mat-grid-tile [colspan]="2">
<h5 style="display: inline-block; margin-right: 5px; position: relative; top: 5px;">{{queueNumber}}.</h5>
</mat-grid-tile>
<mat-grid-tile [colspan]="6">
<div style="display: inline-block; text-align: center;">ID: {{url_id}}</div>
</mat-grid-tile>
<mat-grid-tile [colspan]="13">
<mat-progress-bar style="width: 80%" [value]="download.percent_complete" [mode]="(download.percent_complete === 0) ? 'indeterminate' : 'determinate'"></mat-progress-bar>
</mat-grid-tile>
<mat-grid-tile [colspan]="3">
<button (click)="cancelTheDownload()" mat-icon-button color="warn"><mat-icon fontSet="material-icons-outlined">cancel</mat-icon></button>
</mat-grid-tile>
</mat-grid-list>
</div>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DownloadItemComponent } from './download-item.component';
describe('DownloadItemComponent', () => {
let component: DownloadItemComponent;
let fixture: ComponentFixture<DownloadItemComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DownloadItemComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DownloadItemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,41 @@
import { Component, OnInit, Output, EventEmitter, Input } from '@angular/core';
import { Download } from 'app/main/main.component';
@Component({
selector: 'app-download-item',
templateUrl: './download-item.component.html',
styleUrls: ['./download-item.component.scss']
})
export class DownloadItemComponent implements OnInit {
@Input() download: Download = {
uid: null,
type: 'audio',
percent_complete: 0,
url: 'http://youtube.com/watch?v=17848rufj',
downloading: true,
is_playlist: false
};
@Output() cancelDownload = new EventEmitter<Download>();
@Input() queueNumber = null;
url_id = null;
constructor() { }
ngOnInit() {
if (this.download && this.download.url && this.download.url.includes('youtube')) {
const string_id = (this.download.is_playlist ? '?list=' : '?v=')
const index_offset = (this.download.is_playlist ? 6 : 3);
const end_index = this.download.url.indexOf(string_id) + index_offset;
this.url_id = this.download.url.substring(end_index, this.download.url.length);
}
}
cancelTheDownload() {
this.cancelDownload.emit(this.download);
}
}

View File

@@ -50,6 +50,7 @@
</form> </form>
<br/> <br/>
<mat-checkbox [disabled]="current_download" (change)="videoModeChanged($event)" [(ngModel)]="audioOnly" style="float: left; margin-top: -12px">Only Audio</mat-checkbox> <mat-checkbox [disabled]="current_download" (change)="videoModeChanged($event)" [(ngModel)]="audioOnly" style="float: left; margin-top: -12px">Only Audio</mat-checkbox>
<mat-checkbox [disabled]="current_download" (change)="multiDownloadModeChanged($event)" [(ngModel)]="multiDownloadMode" style="float: right; margin-top: -12px">Multi-download mode</mat-checkbox>
</div> </div>
</mat-card-content> </mat-card-content>
@@ -60,6 +61,18 @@
</mat-card-actions> </mat-card-actions>
</mat-card> </mat-card>
</div> </div>
<div *ngIf="downloads.length > 0 && !current_download" style="margin-top: 15px;" class="big demo-basic">
<mat-card id="card" style="margin-right: 20px; margin-left: 20px;">
<div class="container">
<div *ngFor="let download of downloads; let i = index;" class="row">
<ng-container *ngIf="current_download !== download">
<app-download-item style="width: 100%" [download]="download" [queueNumber]="i+1" (cancelDownload)="cancelDownload($event)"></app-download-item>
<mat-divider style="position: relative" *ngIf="i !== downloads.length - 1"></mat-divider>
</ng-container>
</div>
</div>
</mat-card>
</div>
<br/> <br/>
<div class="centered big" id="bar_div" *ngIf="current_download && current_download.downloading; else nofile"> <div class="centered big" id="bar_div" *ngIf="current_download && current_download.downloading; else nofile">
<div class="margined"> <div class="margined">
@@ -81,7 +94,7 @@
</ng-template> </ng-template>
<div style="margin: 20px" *ngIf="fileManagerEnabled"> <div style="margin: 20px" *ngIf="fileManagerEnabled">
<mat-accordion> <mat-accordion>
<mat-expansion-panel (mouseleave)="accordionLeft('audio')" (mouseenter)="accordionEntered('audio')" class="big"> <mat-expansion-panel (opened)="accordionOpened('audio')" (closed)="accordionClosed('audio')" (mouseleave)="accordionLeft('audio')" (mouseenter)="accordionEntered('audio')" class="big">
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title> <mat-panel-title>
Audio Audio
@@ -116,7 +129,7 @@
</div> </div>
</mat-expansion-panel> </mat-expansion-panel>
<mat-expansion-panel (mouseleave)="accordionLeft('video')" (mouseenter)="accordionEntered('video')" class="big"> <mat-expansion-panel (opened)="accordionOpened('video')" (closed)="accordionClosed('video')" (mouseleave)="accordionLeft('video')" (mouseenter)="accordionEntered('video')" class="big">
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title> <mat-panel-title>
Video Video

View File

@@ -22,6 +22,8 @@ import { v4 as uuid } from 'uuid';
export let audioFilesMouseHovering = false; export let audioFilesMouseHovering = false;
export let videoFilesMouseHovering = false; export let videoFilesMouseHovering = false;
export let audioFilesOpened = false;
export let videoFilesOpened = false;
export interface Download { export interface Download {
uid: string; uid: string;
@@ -44,6 +46,7 @@ export class MainComponent implements OnInit {
determinateProgress = false; determinateProgress = false;
downloadingfile = false; downloadingfile = false;
audioOnly: boolean; audioOnly: boolean;
multiDownloadMode = false;
urlError = false; urlError = false;
path = ''; path = '';
url = ''; url = '';
@@ -179,11 +182,19 @@ export class MainComponent implements OnInit {
last_valid_url = ''; last_valid_url = '';
last_url_check = 0; last_url_check = 0;
test_download: Download = {
uid: null,
type: 'audio',
percent_complete: 0,
url: 'http://youtube.com/watch?v=17848rufj',
downloading: true,
is_playlist: false
};
constructor(private postsService: PostsService, private youtubeSearch: YoutubeSearchService, public snackBar: MatSnackBar, constructor(private postsService: PostsService, private youtubeSearch: YoutubeSearchService, public snackBar: MatSnackBar,
private router: Router, public dialog: MatDialog, private platform: Platform) { private router: Router, public dialog: MatDialog, private platform: Platform) {
this.audioOnly = false; this.audioOnly = false;
// loading config // loading config
this.postsService.loadNavItems().subscribe(result => { // loads settings this.postsService.loadNavItems().subscribe(result => { // loads settings
const backendUrl = result['YoutubeDLMaterial']['Host']['backendurl']; const backendUrl = result['YoutubeDLMaterial']['Host']['backendurl'];
@@ -350,6 +361,10 @@ export class MainComponent implements OnInit {
if (localStorage.getItem('audioOnly') !== null) { if (localStorage.getItem('audioOnly') !== null) {
this.audioOnly = localStorage.getItem('audioOnly') === 'true'; this.audioOnly = localStorage.getItem('audioOnly') === 'true';
} }
if (localStorage.getItem('multiDownloadMode') !== null) {
this.multiDownloadMode = localStorage.getItem('multiDownloadMode') === 'true';
}
} }
// download helpers // download helpers
@@ -359,7 +374,7 @@ export class MainComponent implements OnInit {
if (new_download && this.current_download !== new_download) { if (new_download && this.current_download !== new_download) {
// console.log('mismatched downloads'); // console.log('mismatched downloads');
} else { } else if (!this.multiDownloadMode) {
// if download only mode, just download the file. no redirect // if download only mode, just download the file. no redirect
if (forceView === false && this.downloadOnlyMode && !this.iOS) { if (forceView === false && this.downloadOnlyMode && !this.iOS) {
if (is_playlist) { if (is_playlist) {
@@ -379,9 +394,17 @@ export class MainComponent implements OnInit {
} }
} }
// remove download from current downloads
this.removeDownloadFromCurrentDownloads(new_download);
// reloads mp3s // reloads mp3s
if (this.fileManagerEnabled) { if (this.fileManagerEnabled) {
this.getMp3s(); this.getMp3s();
setTimeout(() => {
this.audioFileCards.forEach(filecard => {
filecard.onHoverResponse();
});
}, 200);
} }
} }
@@ -390,7 +413,7 @@ export class MainComponent implements OnInit {
if (new_download && this.current_download !== new_download) { if (new_download && this.current_download !== new_download) {
// console.log('mismatched downloads'); // console.log('mismatched downloads');
} else { } else if (!this.multiDownloadMode) {
// if download only mode, just download the file. no redirect // if download only mode, just download the file. no redirect
if (forceView === false && this.downloadOnlyMode) { if (forceView === false && this.downloadOnlyMode) {
if (is_playlist) { if (is_playlist) {
@@ -410,9 +433,17 @@ export class MainComponent implements OnInit {
} }
} }
// remove download from current downloads
this.removeDownloadFromCurrentDownloads(new_download);
// reloads mp4s // reloads mp4s
if (this.fileManagerEnabled) { if (this.fileManagerEnabled) {
this.getMp4s(); this.getMp4s();
setTimeout(() => {
this.videoFileCards.forEach(filecard => {
filecard.onHoverResponse();
});
}, 200);
} }
} }
@@ -433,7 +464,7 @@ export class MainComponent implements OnInit {
is_playlist: this.url.includes('playlist') is_playlist: this.url.includes('playlist')
}; };
this.downloads.push(new_download); this.downloads.push(new_download);
if (!this.current_download) { this.current_download = new_download }; if (!this.current_download && !this.multiDownloadMode) { this.current_download = new_download };
this.downloadingfile = true; this.downloadingfile = true;
let customQualityConfiguration = null; let customQualityConfiguration = null;
@@ -471,7 +502,7 @@ export class MainComponent implements OnInit {
is_playlist: this.url.includes('playlist') is_playlist: this.url.includes('playlist')
}; };
this.downloads.push(new_download); this.downloads.push(new_download);
if (!this.current_download) { this.current_download = new_download }; if (!this.current_download && !this.multiDownloadMode) { this.current_download = new_download };
this.downloadingfile = true; this.downloadingfile = true;
let customQualityConfiguration = null; let customQualityConfiguration = null;
@@ -500,13 +531,23 @@ export class MainComponent implements OnInit {
this.openSnackBar('Download failed!', 'OK.'); this.openSnackBar('Download failed!', 'OK.');
}); });
} }
if (this.multiDownloadMode) {
this.url = '';
this.downloadingfile = false;
}
} else { } else {
this.urlError = true; this.urlError = true;
} }
} }
// download canceled handler // download canceled handler
cancelDownload() { cancelDownload(download_to_cancel = null) {
// if one is provided, cancel that one. otherwise, remove the current one
if (download_to_cancel) {
this.removeDownloadFromCurrentDownloads(download_to_cancel)
return;
}
this.downloadingfile = false; this.downloadingfile = false;
this.current_download.downloading = false; this.current_download.downloading = false;
this.current_download = null; this.current_download = null;
@@ -521,6 +562,16 @@ export class MainComponent implements OnInit {
} }
} }
removeDownloadFromCurrentDownloads(download_to_remove) {
const index = this.downloads.indexOf(download_to_remove);
if (index !== -1) {
this.downloads.splice(index, 1);
return true;
} else {
return false;
}
}
downloadAudioFile(name) { downloadAudioFile(name) {
this.downloading_content['audio'][name] = true; this.downloading_content['audio'][name] = true;
this.postsService.downloadFileFromServer(name, 'audio').subscribe(res => { this.postsService.downloadFileFromServer(name, 'audio').subscribe(res => {
@@ -687,6 +738,10 @@ export class MainComponent implements OnInit {
localStorage.setItem('audioOnly', new_val.checked.toString()); localStorage.setItem('audioOnly', new_val.checked.toString());
} }
multiDownloadModeChanged(new_val) {
localStorage.setItem('multiDownloadMode', new_val.checked.toString());
}
getAudioAndVideoFormats(formats): any[] { getAudioAndVideoFormats(formats): any[] {
const audio_formats = {}; const audio_formats = {};
const video_formats = {}; const video_formats = {};
@@ -770,6 +825,22 @@ export class MainComponent implements OnInit {
} }
} }
accordionOpened(type) {
if (type === 'audio') {
audioFilesOpened = true;
} else if (type === 'video') {
videoFilesOpened = true;
}
}
accordionClosed(type) {
if (type === 'audio') {
audioFilesOpened = false;
} else if (type === 'video') {
videoFilesOpened = false;
}
}
// creating a playlist // creating a playlist
openCreatePlaylistDialog(type) { openCreatePlaylistDialog(type) {
const dialogRef = this.dialog.open(CreatePlaylistComponent, { const dialogRef = this.dialog.open(CreatePlaylistComponent, {

View File

@@ -18,7 +18,8 @@
"title_top": "Youtube Downloader", "title_top": "Youtube Downloader",
"file_manager_enabled": true, "file_manager_enabled": true,
"allow_quality_select": true, "allow_quality_select": true,
"download_only_mode": false "download_only_mode": false,
"allow_multi_download_mode": true
}, },
"API": { "API": {
"use_youtube_API": false, "use_youtube_API": false,

View File

@@ -10,7 +10,7 @@
<link href="https://fonts.googleapis.com/css?family=Roboto&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Roboto&display=swap" rel="stylesheet">
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <link href="https://fonts.googleapis.com/icon?family=Material+Icons|Material+Icons+Outlined" rel="stylesheet"/>
</head> </head>
<body> <body>
<app-root></app-root> <app-root></app-root>