mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-04-11 18:51:28 +03:00
Merge pull request #32 from Tzahi12345/subscriptions-v2
Subscriptions v2
This commit is contained in:
@@ -84,12 +84,17 @@ app.use(bodyParser.json());
|
|||||||
|
|
||||||
// objects
|
// objects
|
||||||
|
|
||||||
function File(id, title, thumbnailURL, isAudio, duration) {
|
function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.thumbnailURL = thumbnailURL;
|
this.thumbnailURL = thumbnailURL;
|
||||||
this.isAudio = isAudio;
|
this.isAudio = isAudio;
|
||||||
this.duration = duration;
|
this.duration = duration;
|
||||||
|
this.url = url;
|
||||||
|
this.uploader = uploader;
|
||||||
|
this.size = size;
|
||||||
|
this.path = path;
|
||||||
|
this.upload_date = upload_date;
|
||||||
}
|
}
|
||||||
|
|
||||||
// actual functions
|
// actual functions
|
||||||
@@ -353,10 +358,16 @@ function getVideoFormatID(name)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createPlaylistZipFile(fileNames, type, outputName) {
|
async function createPlaylistZipFile(fileNames, type, outputName, fullPathProvided = null) {
|
||||||
return new Promise(async resolve => {
|
return new Promise(async resolve => {
|
||||||
let zipFolderPath = path.join(__dirname, (type === 'audio') ? audioFolderPath : videoFolderPath);
|
let zipFolderPath = null;
|
||||||
// let name = fileNames[0].split(' ')[0] + fileNames[1].split(' ')[0];
|
|
||||||
|
if (!fullPathProvided) {
|
||||||
|
zipFolderPath = path.join(__dirname, (type === 'audio') ? audioFolderPath : videoFolderPath);
|
||||||
|
} else {
|
||||||
|
zipFolderPath = path.join(__dirname, config_api.getConfigItem('ytdl_subscriptions_base_path'));
|
||||||
|
}
|
||||||
|
|
||||||
let ext = (type === 'audio') ? '.mp3' : '.mp4';
|
let ext = (type === 'audio') ? '.mp3' : '.mp4';
|
||||||
|
|
||||||
let output = fs.createWriteStream(path.join(zipFolderPath, outputName + '.zip'));
|
let output = fs.createWriteStream(path.join(zipFolderPath, outputName + '.zip'));
|
||||||
@@ -376,7 +387,8 @@ async function createPlaylistZipFile(fileNames, type, outputName) {
|
|||||||
|
|
||||||
for (let i = 0; i < fileNames.length; i++) {
|
for (let i = 0; i < fileNames.length; i++) {
|
||||||
let fileName = fileNames[i];
|
let fileName = fileNames[i];
|
||||||
archive.file(zipFolderPath + fileName + ext, {name: fileName + ext})
|
let file_path = !fullPathProvided ? zipFolderPath + fileName + ext : fileName;
|
||||||
|
archive.file(file_path, {name: fileName + ext})
|
||||||
}
|
}
|
||||||
|
|
||||||
await archive.finalize();
|
await archive.finalize();
|
||||||
@@ -951,20 +963,24 @@ app.post('/api/getMp3s', function(req, res) {
|
|||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
let file = files[i];
|
let file = files[i];
|
||||||
var file_path = file.substring(audioFolderPath.length, file.length);
|
var file_path = file.substring(audioFolderPath.length, file.length);
|
||||||
|
|
||||||
|
var stats = fs.statSync(file);
|
||||||
|
|
||||||
var id = file_path.substring(0, file_path.length-4);
|
var id = file_path.substring(0, file_path.length-4);
|
||||||
var jsonobj = getJSONMp3(id);
|
var jsonobj = getJSONMp3(id);
|
||||||
if (!jsonobj) continue;
|
if (!jsonobj) continue;
|
||||||
var title = jsonobj.title;
|
var title = jsonobj.title;
|
||||||
|
var url = jsonobj.webpage_url;
|
||||||
|
var uploader = jsonobj.uploader;
|
||||||
|
var upload_date = jsonobj.upload_date;
|
||||||
|
upload_date = `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}`;
|
||||||
|
|
||||||
if (title.length > 14) // edits title if it's too long
|
var size = stats.size;
|
||||||
{
|
|
||||||
title = title.substring(0,12) + "...";
|
|
||||||
}
|
|
||||||
|
|
||||||
var thumbnail = jsonobj.thumbnail;
|
var thumbnail = jsonobj.thumbnail;
|
||||||
var duration = jsonobj.duration;
|
var duration = jsonobj.duration;
|
||||||
var isaudio = true;
|
var isaudio = true;
|
||||||
var file_obj = new File(id, title, thumbnail, isaudio, duration);
|
var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date);
|
||||||
mp3s.push(file_obj);
|
mp3s.push(file_obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -984,20 +1000,24 @@ app.post('/api/getMp4s', function(req, res) {
|
|||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
let file = files[i];
|
let file = files[i];
|
||||||
var file_path = file.substring(videoFolderPath.length, file.length);
|
var file_path = file.substring(videoFolderPath.length, file.length);
|
||||||
|
|
||||||
|
var stats = fs.statSync(file);
|
||||||
|
|
||||||
var id = file_path.substring(0, file_path.length-4);
|
var id = file_path.substring(0, file_path.length-4);
|
||||||
var jsonobj = getJSONMp4(id);
|
var jsonobj = getJSONMp4(id);
|
||||||
if (!jsonobj) continue;
|
if (!jsonobj) continue;
|
||||||
var title = jsonobj.title;
|
var title = jsonobj.title;
|
||||||
|
var url = jsonobj.webpage_url;
|
||||||
if (title.length > 14) // edits title if it's too long
|
var uploader = jsonobj.uploader;
|
||||||
{
|
var upload_date = jsonobj.upload_date;
|
||||||
title = title.substring(0,12) + "...";
|
upload_date = `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}`;
|
||||||
}
|
|
||||||
|
|
||||||
var thumbnail = jsonobj.thumbnail;
|
var thumbnail = jsonobj.thumbnail;
|
||||||
var duration = jsonobj.duration;
|
var duration = jsonobj.duration;
|
||||||
|
|
||||||
|
var size = stats.size;
|
||||||
|
|
||||||
var isaudio = false;
|
var isaudio = false;
|
||||||
var file_obj = new File(id, title, thumbnail, isaudio, duration);
|
var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date);
|
||||||
mp4s.push(file_obj);
|
mp4s.push(file_obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1101,6 +1121,8 @@ app.post('/api/getSubscription', async (req, res) => {
|
|||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
let file = files[i];
|
let file = files[i];
|
||||||
var file_path = file.substring(appended_base_path.length, file.length);
|
var file_path = file.substring(appended_base_path.length, file.length);
|
||||||
|
var stats = fs.statSync(file);
|
||||||
|
|
||||||
var id = file_path.substring(0, file_path.length-4);
|
var id = file_path.substring(0, file_path.length-4);
|
||||||
var jsonobj = getJSONMp4(id, appended_base_path);
|
var jsonobj = getJSONMp4(id, appended_base_path);
|
||||||
if (!jsonobj) continue;
|
if (!jsonobj) continue;
|
||||||
@@ -1108,8 +1130,14 @@ app.post('/api/getSubscription', async (req, res) => {
|
|||||||
|
|
||||||
var thumbnail = jsonobj.thumbnail;
|
var thumbnail = jsonobj.thumbnail;
|
||||||
var duration = jsonobj.duration;
|
var duration = jsonobj.duration;
|
||||||
|
var url = jsonobj.webpage_url;
|
||||||
|
var uploader = jsonobj.uploader;
|
||||||
|
var upload_date = jsonobj.upload_date;
|
||||||
|
upload_date = `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}`;
|
||||||
|
var size = stats.size;
|
||||||
|
|
||||||
var isaudio = false;
|
var isaudio = false;
|
||||||
var file_obj = new File(id, title, thumbnail, isaudio, duration);
|
var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date);
|
||||||
parsed_files.push(file_obj);
|
parsed_files.push(file_obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1120,9 +1148,6 @@ app.post('/api/getSubscription', async (req, res) => {
|
|||||||
} else {
|
} else {
|
||||||
res.sendStatus(500);
|
res.sendStatus(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/downloadVideosForSubscription', async (req, res) => {
|
app.post('/api/downloadVideosForSubscription', async (req, res) => {
|
||||||
@@ -1257,11 +1282,12 @@ app.post('/api/deleteMp4', async (req, res) => {
|
|||||||
|
|
||||||
app.post('/api/downloadFile', async (req, res) => {
|
app.post('/api/downloadFile', async (req, res) => {
|
||||||
let fileNames = req.body.fileNames;
|
let fileNames = req.body.fileNames;
|
||||||
let is_playlist = req.body.is_playlist;
|
let zip_mode = req.body.zip_mode;
|
||||||
let type = req.body.type;
|
let type = req.body.type;
|
||||||
let outputName = req.body.outputName;
|
let outputName = req.body.outputName;
|
||||||
|
let fullPathProvided = req.body.fullPathProvided;
|
||||||
let file = null;
|
let file = null;
|
||||||
if (!is_playlist) {
|
if (!zip_mode) {
|
||||||
fileNames = decodeURIComponent(fileNames);
|
fileNames = decodeURIComponent(fileNames);
|
||||||
if (type === 'audio') {
|
if (type === 'audio') {
|
||||||
file = __dirname + '/' + audioFolderPath + fileNames + '.mp3';
|
file = __dirname + '/' + audioFolderPath + fileNames + '.mp3';
|
||||||
@@ -1272,10 +1298,20 @@ app.post('/api/downloadFile', async (req, res) => {
|
|||||||
for (let i = 0; i < fileNames.length; i++) {
|
for (let i = 0; i < fileNames.length; i++) {
|
||||||
fileNames[i] = decodeURIComponent(fileNames[i]);
|
fileNames[i] = decodeURIComponent(fileNames[i]);
|
||||||
}
|
}
|
||||||
file = await createPlaylistZipFile(fileNames, type, outputName);
|
file = await createPlaylistZipFile(fileNames, type, outputName, fullPathProvided);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.sendFile(file);
|
res.sendFile(file, function (err) {
|
||||||
|
if (err) {
|
||||||
|
next(err);
|
||||||
|
} else if (fullPathProvided) {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(file);
|
||||||
|
} catch(e) {
|
||||||
|
console.log("ERROR: Failed to remove file", file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/deleteFile', async (req, res) => {
|
app.post('/api/deleteFile', async (req, res) => {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
"@locl/core": "0.0.1-beta.2",
|
"@locl/core": "0.0.1-beta.2",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.2",
|
||||||
|
"filesize": "^6.1.0",
|
||||||
"ng-lazyload-image": "^7.0.1",
|
"ng-lazyload-image": "^7.0.1",
|
||||||
"ng4-configure": "^0.1.7",
|
"ng4-configure": "^0.1.7",
|
||||||
"ngx-content-loading": "^0.1.3",
|
"ngx-content-loading": "^0.1.3",
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import { SettingsComponent } from './settings/settings.component';
|
|||||||
|
|
||||||
import es from '@angular/common/locales/es';
|
import es from '@angular/common/locales/es';
|
||||||
import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.component';
|
import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.component';
|
||||||
|
import { VideoInfoDialogComponent } from './dialogs/video-info-dialog/video-info-dialog.component';
|
||||||
registerLocaleData(es, 'es');
|
registerLocaleData(es, 'es');
|
||||||
|
|
||||||
export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps<any>) {
|
export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps<any>) {
|
||||||
@@ -70,7 +71,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
|||||||
SubscriptionFileCardComponent,
|
SubscriptionFileCardComponent,
|
||||||
SubscriptionInfoDialogComponent,
|
SubscriptionInfoDialogComponent,
|
||||||
SettingsComponent,
|
SettingsComponent,
|
||||||
AboutDialogComponent
|
AboutDialogComponent,
|
||||||
|
VideoInfoDialogComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<h4 mat-dialog-title>{{file.title}}</h4>
|
||||||
|
|
||||||
|
<mat-dialog-content>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label"><strong><ng-container i18n="Video name property">Name:</ng-container> </strong></div>
|
||||||
|
<div class="info-item-value">{{file.title}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label"><strong><ng-container i18n="Video URL property">URL:</ng-container> </strong></div>
|
||||||
|
<div class="info-item-value"><a target="_blank" [href]="file.url">{{file.url}}</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label"><strong><ng-container i18n="Video ID property">Uploader:</ng-container> </strong></div>
|
||||||
|
<div class="info-item-value">{{file.uploader ? file.uploader : 'N/A'}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label"><strong><ng-container i18n="Video file size property">File size:</ng-container> </strong></div>
|
||||||
|
<div class="info-item-value">{{filesize(file.size)}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label"><strong><ng-container i18n="Video path property">Path:</ng-container> </strong></div>
|
||||||
|
<div class="info-item-value">{{file.path}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-item-label"><strong><ng-container i18n="Video upload date property">Upload Date:</ng-container> </strong></div>
|
||||||
|
<div class="info-item-value">{{file.upload_date}}</div>
|
||||||
|
</div>
|
||||||
|
</mat-dialog-content>
|
||||||
|
|
||||||
|
<mat-dialog-actions>
|
||||||
|
<button mat-button mat-dialog-close><ng-container i18n="Close subscription info button">Close</ng-container></button>
|
||||||
|
</mat-dialog-actions>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
.info-item {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item-value {
|
||||||
|
font-size: 13px;
|
||||||
|
display: inline-block;
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {flex: 1 1 auto;}
|
||||||
|
|
||||||
|
.info-item-label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 30%;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { VideoInfoDialogComponent } from './video-info-dialog.component';
|
||||||
|
|
||||||
|
describe('VideoInfoDialogComponent', () => {
|
||||||
|
let component: VideoInfoDialogComponent;
|
||||||
|
let fixture: ComponentFixture<VideoInfoDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ VideoInfoDialogComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(VideoInfoDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Component, OnInit, Inject } from '@angular/core';
|
||||||
|
import filesize from 'filesize';
|
||||||
|
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-video-info-dialog',
|
||||||
|
templateUrl: './video-info-dialog.component.html',
|
||||||
|
styleUrls: ['./video-info-dialog.component.scss']
|
||||||
|
})
|
||||||
|
export class VideoInfoDialogComponent implements OnInit {
|
||||||
|
file: any;
|
||||||
|
filesize;
|
||||||
|
constructor(@Inject(MAT_DIALOG_DATA) public data: any) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.filesize = filesize;
|
||||||
|
if (this.data) {
|
||||||
|
this.file = this.data.file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -51,6 +51,14 @@
|
|||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-link {
|
||||||
|
width: 80%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 576px){
|
@media (max-width: 576px){
|
||||||
|
|
||||||
.example-card {
|
.example-card {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<mat-card class="example-card mat-elevation-z6">
|
<mat-card class="example-card mat-elevation-z6">
|
||||||
<div style="padding:5px">
|
<div style="padding:5px">
|
||||||
<div style="height: 52px;">
|
<div style="height: 52px;">
|
||||||
<b><a href="javascript:void(0)" (click)="!isPlaylist ? mainComponent.goToFile(name, isAudio) : mainComponent.goToPlaylist(name, type)">{{title}}</a></b>
|
<div>
|
||||||
<br/>
|
<b><a class="file-link" href="javascript:void(0)" (click)="!isPlaylist ? mainComponent.goToFile(name, isAudio) : mainComponent.goToPlaylist(name, type)">{{title}}</a></b>
|
||||||
|
</div>
|
||||||
<span class="max-two-lines"><ng-container i18n="File or playlist ID">ID:</ng-container> {{name}}</span>
|
<span class="max-two-lines"><ng-container i18n="File or playlist ID">ID:</ng-container> {{name}}</span>
|
||||||
<div *ngIf="isPlaylist"><ng-container i18n="Playlist video count">Count:</ng-container> {{count}}</div>
|
<div *ngIf="isPlaylist"><ng-container i18n="Playlist video count">Count:</ng-container> {{count}}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -15,10 +16,11 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button *ngIf="!use_youtubedl_archive" (click)="deleteFile()" class="deleteButton" mat-icon-button><mat-icon>delete_forever</mat-icon></button>
|
<button *ngIf="isPlaylist" (click)="deleteFile()" class="deleteButton" mat-icon-button><mat-icon>delete_forever</mat-icon></button>
|
||||||
<button [matMenuTriggerFor]="action_menu" *ngIf="use_youtubedl_archive" class="deleteButton" mat-icon-button><mat-icon>more_vert</mat-icon></button>
|
<button [matMenuTriggerFor]="action_menu" *ngIf="!isPlaylist" class="deleteButton" mat-icon-button><mat-icon>more_vert</mat-icon></button>
|
||||||
<mat-menu #action_menu="matMenu">
|
<mat-menu #action_menu="matMenu">
|
||||||
|
<button (click)="openVideoInfoDialog()" mat-menu-item><mat-icon>info</mat-icon><ng-container i18n="Video info button">Info</ng-container></button>
|
||||||
<button (click)="deleteFile()" mat-menu-item><mat-icon>delete</mat-icon><ng-container i18n="Delete video button">Delete</ng-container></button>
|
<button (click)="deleteFile()" mat-menu-item><mat-icon>delete</mat-icon><ng-container i18n="Delete video button">Delete</ng-container></button>
|
||||||
<button (click)="deleteFile(true)" mat-menu-item><mat-icon>delete_forever</mat-icon><ng-container i18n="Delete and blacklist video button">Delete and blacklist</ng-container></button>
|
<button *ngIf="use_youtubedl_archive" (click)="deleteFile(true)" mat-menu-item><mat-icon>delete_forever</mat-icon><ng-container i18n="Delete and blacklist video button">Delete and blacklist</ng-container></button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import {EventEmitter} from '@angular/core';
|
|||||||
import { MainComponent } from 'app/main/main.component';
|
import { MainComponent } from 'app/main/main.component';
|
||||||
import { Subject, Observable } from 'rxjs';
|
import { Subject, Observable } from 'rxjs';
|
||||||
import 'rxjs/add/observable/merge';
|
import 'rxjs/add/observable/merge';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { VideoInfoDialogComponent } from 'app/dialogs/video-info-dialog/video-info-dialog.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-file-card',
|
selector: 'app-file-card',
|
||||||
@@ -12,7 +14,7 @@ import 'rxjs/add/observable/merge';
|
|||||||
styleUrls: ['./file-card.component.css']
|
styleUrls: ['./file-card.component.css']
|
||||||
})
|
})
|
||||||
export class FileCardComponent implements OnInit {
|
export class FileCardComponent implements OnInit {
|
||||||
|
@Input() file: any;
|
||||||
@Input() title: string;
|
@Input() title: string;
|
||||||
@Input() length: string;
|
@Input() length: string;
|
||||||
@Input() name: string;
|
@Input() name: string;
|
||||||
@@ -29,8 +31,10 @@ export class FileCardComponent implements OnInit {
|
|||||||
scrollSubject;
|
scrollSubject;
|
||||||
scrollAndLoad;
|
scrollAndLoad;
|
||||||
|
|
||||||
constructor(private postsService: PostsService, public snackBar: MatSnackBar, public mainComponent: MainComponent) {
|
constructor(private postsService: PostsService, public snackBar: MatSnackBar, public mainComponent: MainComponent,
|
||||||
this.scrollSubject = new Subject();
|
private dialog: MatDialog) {
|
||||||
|
|
||||||
|
this.scrollSubject = new Subject();
|
||||||
this.scrollAndLoad = Observable.merge(
|
this.scrollAndLoad = Observable.merge(
|
||||||
Observable.fromEvent(window, 'scroll'),
|
Observable.fromEvent(window, 'scroll'),
|
||||||
this.scrollSubject
|
this.scrollSubject
|
||||||
@@ -57,6 +61,15 @@ export class FileCardComponent implements OnInit {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openVideoInfoDialog() {
|
||||||
|
const dialogRef = this.dialog.open(VideoInfoDialogComponent, {
|
||||||
|
data: {
|
||||||
|
file: this.file,
|
||||||
|
},
|
||||||
|
minWidth: '50vw'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onImgError(event) {
|
onImgError(event) {
|
||||||
this.image_errored = true;
|
this.image_errored = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,7 +203,7 @@
|
|||||||
<div *ngIf="mp3s.length > 0;else nomp3s">
|
<div *ngIf="mp3s.length > 0;else nomp3s">
|
||||||
<mat-grid-list style="margin-bottom: 15px;" (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
<mat-grid-list style="margin-bottom: 15px;" (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
||||||
<mat-grid-tile *ngFor="let file of mp3s; index as i;">
|
<mat-grid-tile *ngFor="let file of mp3s; index as i;">
|
||||||
<app-file-card #audiofilecard (removeFile)="removeFromMp3($event)" [title]="file.title" [name]="file.id" [thumbnailURL]="file.thumbnailURL"
|
<app-file-card #audiofilecard (removeFile)="removeFromMp3($event)" [file]="file" [title]="file.title" [name]="file.id" [thumbnailURL]="file.thumbnailURL"
|
||||||
[length]="file.duration" [isAudio]="true" [use_youtubedl_archive]="use_youtubedl_archive"></app-file-card>
|
[length]="file.duration" [isAudio]="true" [use_youtubedl_archive]="use_youtubedl_archive"></app-file-card>
|
||||||
<mat-progress-bar *ngIf="downloading_content['audio'][file.id]" class="download-progress-bar" mode="indeterminate"></mat-progress-bar>
|
<mat-progress-bar *ngIf="downloading_content['audio'][file.id]" class="download-progress-bar" mode="indeterminate"></mat-progress-bar>
|
||||||
</mat-grid-tile>
|
</mat-grid-tile>
|
||||||
@@ -244,7 +244,7 @@
|
|||||||
<div *ngIf="mp4s.length > 0;else nomp4s">
|
<div *ngIf="mp4s.length > 0;else nomp4s">
|
||||||
<mat-grid-list style="margin-bottom: 15px;" (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
<mat-grid-list style="margin-bottom: 15px;" (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
||||||
<mat-grid-tile *ngFor="let file of mp4s; index as i;">
|
<mat-grid-tile *ngFor="let file of mp4s; index as i;">
|
||||||
<app-file-card #videofilecard (removeFile)="removeFromMp4($event)" [title]="file.title" [name]="file.id" [thumbnailURL]="file.thumbnailURL"
|
<app-file-card #videofilecard (removeFile)="removeFromMp4($event)" [file]="file" [title]="file.title" [name]="file.id" [thumbnailURL]="file.thumbnailURL"
|
||||||
[length]="file.duration" [isAudio]="false" [use_youtubedl_archive]="use_youtubedl_archive"></app-file-card>
|
[length]="file.duration" [isAudio]="false" [use_youtubedl_archive]="use_youtubedl_archive"></app-file-card>
|
||||||
<mat-progress-bar *ngIf="downloading_content['video'][file.id]" class="download-progress-bar" mode="indeterminate"></mat-progress-bar>
|
<mat-progress-bar *ngIf="downloading_content['video'][file.id]" class="download-progress-bar" mode="indeterminate"></mat-progress-bar>
|
||||||
</mat-grid-tile>
|
</mat-grid-tile>
|
||||||
|
|||||||
@@ -114,11 +114,12 @@ export class PostsService {
|
|||||||
return this.http.post(this.path + 'getMp4s', {});
|
return this.http.post(this.path + 'getMp4s', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadFileFromServer(fileName, type, outputName = null) {
|
downloadFileFromServer(fileName, type, outputName = null, fullPathProvided = null) {
|
||||||
return this.http.post(this.path + 'downloadFile', {fileNames: fileName,
|
return this.http.post(this.path + 'downloadFile', {fileNames: fileName,
|
||||||
type: type,
|
type: type,
|
||||||
is_playlist: Array.isArray(fileName),
|
zip_mode: Array.isArray(fileName),
|
||||||
outputName: outputName},
|
outputName: outputName,
|
||||||
|
fullPathProvided: fullPathProvided},
|
||||||
{responseType: 'blob'});
|
{responseType: 'blob'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<button [matMenuTriggerFor]="action_menu" class="menuButton" mat-icon-button><mat-icon>more_vert</mat-icon></button>
|
<button [matMenuTriggerFor]="action_menu" class="menuButton" mat-icon-button><mat-icon>more_vert</mat-icon></button>
|
||||||
<mat-menu #action_menu="matMenu">
|
<mat-menu #action_menu="matMenu">
|
||||||
|
<button (click)="openSubscriptionInfoDialog()" mat-menu-item><mat-icon>info</mat-icon><ng-container i18n="Subscription video info button">Info</ng-container></button>
|
||||||
<button (click)="deleteAndRedownload()" mat-menu-item><mat-icon>restore</mat-icon><ng-container i18n="Delete and redownload subscription video button">Delete and redownload</ng-container></button>
|
<button (click)="deleteAndRedownload()" mat-menu-item><mat-icon>restore</mat-icon><ng-container i18n="Delete and redownload subscription video button">Delete and redownload</ng-container></button>
|
||||||
<button (click)="deleteForever()" mat-menu-item *ngIf="sub.archive && use_youtubedl_archive"><mat-icon>delete_forever</mat-icon><ng-container i18n="Delete forever subscription video button">Delete forever</ng-container></button>
|
<button (click)="deleteForever()" mat-menu-item *ngIf="sub.archive && use_youtubedl_archive"><mat-icon>delete_forever</mat-icon><ng-container i18n="Delete forever subscription video button">Delete forever</ng-container></button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { Observable, Subject } from 'rxjs';
|
|||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { PostsService } from 'app/posts.services';
|
import { PostsService } from 'app/posts.services';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { VideoInfoDialogComponent } from 'app/dialogs/video-info-dialog/video-info-dialog.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-subscription-file-card',
|
selector: 'app-subscription-file-card',
|
||||||
@@ -25,7 +27,7 @@ export class SubscriptionFileCardComponent implements OnInit {
|
|||||||
@Output() goToFileEmit = new EventEmitter<any>();
|
@Output() goToFileEmit = new EventEmitter<any>();
|
||||||
@Output() reloadSubscription = new EventEmitter<boolean>();
|
@Output() reloadSubscription = new EventEmitter<boolean>();
|
||||||
|
|
||||||
constructor(private snackBar: MatSnackBar, private postsService: PostsService) {
|
constructor(private snackBar: MatSnackBar, private postsService: PostsService, private dialog: MatDialog) {
|
||||||
this.scrollSubject = new Subject();
|
this.scrollSubject = new Subject();
|
||||||
this.scrollAndLoad = Observable.merge(
|
this.scrollAndLoad = Observable.merge(
|
||||||
Observable.fromEvent(window, 'scroll'),
|
Observable.fromEvent(window, 'scroll'),
|
||||||
@@ -55,6 +57,15 @@ export class SubscriptionFileCardComponent implements OnInit {
|
|||||||
this.goToFileEmit.emit(this.file.id);
|
this.goToFileEmit.emit(this.file.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openSubscriptionInfoDialog() {
|
||||||
|
const dialogRef = this.dialog.open(VideoInfoDialogComponent, {
|
||||||
|
data: {
|
||||||
|
file: this.file,
|
||||||
|
},
|
||||||
|
minWidth: '50vw'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
deleteAndRedownload() {
|
deleteAndRedownload() {
|
||||||
this.postsService.deleteSubscriptionFile(this.sub, this.file.id, false).subscribe(res => {
|
this.postsService.deleteSubscriptionFile(this.sub, this.file.id, false).subscribe(res => {
|
||||||
this.reloadSubscription.emit(true);
|
this.reloadSubscription.emit(true);
|
||||||
@@ -77,8 +88,7 @@ export class SubscriptionFileCardComponent implements OnInit {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fancyTimeFormat(time)
|
function fancyTimeFormat(time) {
|
||||||
{
|
|
||||||
// Hours, minutes and seconds
|
// Hours, minutes and seconds
|
||||||
const hrs = ~~(time / 3600);
|
const hrs = ~~(time / 3600);
|
||||||
const mins = ~~((time % 3600) / 60);
|
const mins = ~~((time % 3600) / 60);
|
||||||
|
|||||||
@@ -1,31 +1,46 @@
|
|||||||
<br/>
|
<div style="margin-top: 14px;">
|
||||||
<button class="back-button" (click)="goBack()" mat-icon-button><mat-icon>arrow_back</mat-icon></button>
|
<button class="back-button" (click)="goBack()" mat-icon-button><mat-icon>arrow_back</mat-icon></button>
|
||||||
<div style="margin-bottom: 15px;">
|
<div style="margin-bottom: 15px;">
|
||||||
<h2 style="text-align: center;" *ngIf="subscription">
|
<h2 style="text-align: center;" *ngIf="subscription">
|
||||||
{{subscription.name}}
|
{{subscription.name}}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
|
||||||
<mat-divider style="width: 80%; margin: 0 auto"></mat-divider>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div *ngIf="subscription">
|
|
||||||
<div class="flex-grid">
|
|
||||||
<div class="col"></div>
|
|
||||||
<div class="col">
|
|
||||||
<h4 i18n="Subscription videos title" style="text-align: center; margin-bottom: 20px;">Videos</h4>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<mat-form-field [ngClass]="searchIsFocused ? 'search-bar-focused' : 'search-bar-unfocused'" class="search-bar" color="accent">
|
|
||||||
<input (focus)="searchIsFocused = true" (blur)="searchIsFocused = false" class="search-input" type="text" placeholder="Search" i18n-placeholder="Subscription videos search placeholder" [(ngModel)]="search_text" (ngModelChange)="onSearchInputChanged($event)" matInput>
|
|
||||||
<mat-icon matSuffix>search</mat-icon>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<mat-divider style="width: 80%; margin: 0 auto"></mat-divider>
|
||||||
<div class="row">
|
<br/>
|
||||||
<div *ngFor="let file of filtered_files" class="col-6 col-lg-4 mb-2 mt-2 sub-file-col">
|
|
||||||
<app-subscription-file-card (reloadSubscription)="getSubscription()" (goToFileEmit)="goToFile($event)" [file]="file" [sub]="subscription" [use_youtubedl_archive]="use_youtubedl_archive"></app-subscription-file-card>
|
<div *ngIf="subscription">
|
||||||
|
<div class="flex-grid">
|
||||||
|
<div class="filter-select-parent">
|
||||||
|
<div style="display: inline-block;">
|
||||||
|
<mat-select style="width: 110px;" [(ngModel)]="this.filterProperty" (selectionChange)="filterOptionChanged($event.value)">
|
||||||
|
<mat-option *ngFor="let filterOption of filterProperties | keyvalue" [value]="filterOption.value">
|
||||||
|
{{filterOption['value']['label']}}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</div>
|
||||||
|
<div style="display: inline-block;">
|
||||||
|
<button (click)="toggleModeChange()" mat-icon-button><mat-icon>{{descendingMode ? 'arrow_downward' : 'arrow_upward'}}</mat-icon></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h4 i18n="Subscription videos title" style="text-align: center; margin-bottom: 20px;">Videos</h4>
|
||||||
|
</div>
|
||||||
|
<div style="top: -12px;" class="col">
|
||||||
|
<mat-form-field [ngClass]="searchIsFocused ? 'search-bar-focused' : 'search-bar-unfocused'" class="search-bar" color="accent">
|
||||||
|
<input (focus)="searchIsFocused = true" (blur)="searchIsFocused = false" class="search-input" type="text" placeholder="Search" i18n-placeholder="Subscription videos search placeholder" [(ngModel)]="search_text" (ngModelChange)="onSearchInputChanged($event)" matInput>
|
||||||
|
<mat-icon matSuffix>search</mat-icon>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div *ngFor="let file of filtered_files" class="col-6 col-lg-4 mb-2 mt-2 sub-file-col">
|
||||||
|
<app-subscription-file-card (reloadSubscription)="getSubscription()" (goToFileEmit)="goToFile($event)" [file]="file" [sub]="subscription" [use_youtubedl_archive]="use_youtubedl_archive"></app-subscription-file-card>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="save-button" color="primary" (click)="downloadContent()" [disabled]="downloading" mat-fab><mat-icon class="save-icon">save</mat-icon><mat-spinner *ngIf="downloading" class="spinner" [diameter]="50"></mat-spinner></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -8,6 +8,13 @@
|
|||||||
left: 15px;
|
left: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-select-parent {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 20px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.search-bar {
|
.search-bar {
|
||||||
transition: all .5s ease;
|
transition: all .5s ease;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -29,8 +36,29 @@
|
|||||||
.flex-grid {
|
.flex-grid {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col {
|
.col {
|
||||||
width: 33%;
|
width: 33%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
bottom: 3px;
|
||||||
|
left: 3px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-button {
|
||||||
|
right: 25px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-icon {
|
||||||
|
bottom: 1px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,31 @@ export class SubscriptionComponent implements OnInit {
|
|||||||
search_mode = false;
|
search_mode = false;
|
||||||
search_text = '';
|
search_text = '';
|
||||||
searchIsFocused = false;
|
searchIsFocused = false;
|
||||||
|
descendingMode = true;
|
||||||
|
filterProperties = {
|
||||||
|
'upload_date': {
|
||||||
|
'key': 'upload_date',
|
||||||
|
'label': 'Upload Date',
|
||||||
|
'property': 'upload_date'
|
||||||
|
},
|
||||||
|
'name': {
|
||||||
|
'key': 'name',
|
||||||
|
'label': 'Name',
|
||||||
|
'property': 'title'
|
||||||
|
},
|
||||||
|
'file_size': {
|
||||||
|
'key': 'file_size',
|
||||||
|
'label': 'File Size',
|
||||||
|
'property': 'size'
|
||||||
|
},
|
||||||
|
'duration': {
|
||||||
|
'key': 'duration',
|
||||||
|
'label': 'Duration',
|
||||||
|
'property': 'duration'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
filterProperty = this.filterProperties['upload_date'];
|
||||||
|
downloading = false;
|
||||||
|
|
||||||
constructor(private postsService: PostsService, private route: ActivatedRoute, private router: Router) { }
|
constructor(private postsService: PostsService, private route: ActivatedRoute, private router: Router) { }
|
||||||
|
|
||||||
@@ -27,6 +52,12 @@ export class SubscriptionComponent implements OnInit {
|
|||||||
this.getSubscription();
|
this.getSubscription();
|
||||||
this.getConfig();
|
this.getConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set filter property to cached
|
||||||
|
const cached_filter_property = localStorage.getItem('filter_property');
|
||||||
|
if (cached_filter_property && this.filterProperties[cached_filter_property]) {
|
||||||
|
this.filterProperty = this.filterProperties[cached_filter_property];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
goBack() {
|
goBack() {
|
||||||
@@ -42,6 +73,7 @@ export class SubscriptionComponent implements OnInit {
|
|||||||
} else {
|
} else {
|
||||||
this.filtered_files = this.files;
|
this.filtered_files = this.files;
|
||||||
}
|
}
|
||||||
|
this.filterByProperty(this.filterProperty['property']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,4 +104,40 @@ export class SubscriptionComponent implements OnInit {
|
|||||||
this.filtered_files = this.files.filter(option => option.id.toLowerCase().includes(filterValue));
|
this.filtered_files = this.files.filter(option => option.id.toLowerCase().includes(filterValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filterByProperty(prop) {
|
||||||
|
if (this.descendingMode) {
|
||||||
|
this.filtered_files = this.filtered_files.sort((a, b) => (a[prop] > b[prop] ? -1 : 1));
|
||||||
|
} else {
|
||||||
|
this.filtered_files = this.filtered_files.sort((a, b) => (a[prop] > b[prop] ? 1 : -1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterOptionChanged(value) {
|
||||||
|
// this.filterProperty = value;
|
||||||
|
this.filterByProperty(value['property']);
|
||||||
|
localStorage.setItem('filter_property', value['key']);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleModeChange() {
|
||||||
|
this.descendingMode = !this.descendingMode;
|
||||||
|
this.filterByProperty(this.filterProperty['property']);
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadContent() {
|
||||||
|
const fileNames = [];
|
||||||
|
for (let i = 0; i < this.files.length; i++) {
|
||||||
|
fileNames.push(this.files[i].path);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.downloading = true;
|
||||||
|
this.postsService.downloadFileFromServer(fileNames, 'video', this.subscription.name, true).subscribe(res => {
|
||||||
|
this.downloading = false;
|
||||||
|
const blob: Blob = res;
|
||||||
|
saveAs(blob, this.subscription.name + '.zip');
|
||||||
|
}, err => {
|
||||||
|
console.log(err);
|
||||||
|
this.downloading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ export class SubscriptionsComponent implements OnInit {
|
|||||||
this.postsService.getAllSubscriptions().subscribe(res => {
|
this.postsService.getAllSubscriptions().subscribe(res => {
|
||||||
this.subscriptions_loading = false;
|
this.subscriptions_loading = false;
|
||||||
this.subscriptions = res['subscriptions'];
|
this.subscriptions = res['subscriptions'];
|
||||||
|
if (!this.subscriptions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < this.subscriptions.length; i++) {
|
for (let i = 0; i < this.subscriptions.length; i++) {
|
||||||
const sub = this.subscriptions[i];
|
const sub = this.subscriptions[i];
|
||||||
|
|||||||
Reference in New Issue
Block a user