Audio downloads now work with progress bar, but it requires file conversion at the end. It ends up being around the same speed as the regular method

This commit is contained in:
Tzahi12345
2020-05-03 03:24:25 -04:00
parent 4e6d68d9e6
commit fb23d7c41e
7 changed files with 84 additions and 17 deletions

View File

@@ -5,6 +5,7 @@ var auth_api = require('./authentication/auth');
var winston = require('winston'); var winston = require('winston');
var path = require('path'); var path = require('path');
var youtubedl = require('youtube-dl'); var youtubedl = require('youtube-dl');
var ffmpeg = require('fluent-ffmpeg');
var compression = require('compression'); var compression = require('compression');
var https = require('https'); var https = require('https');
var express = require("express"); var express = require("express");
@@ -1287,6 +1288,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) {
} }
download['complete'] = true; download['complete'] = true;
download['fileNames'] = is_playlist ? file_names : [full_file_path]
updateDownloads(); updateDownloads();
var videopathEncoded = encodeURIComponent(file_names[0]); var videopathEncoded = encodeURIComponent(file_names[0]);
@@ -1305,7 +1307,11 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) {
return new Promise(async resolve => { return new Promise(async resolve => {
var date = Date.now(); var date = Date.now();
var file_uid = null; var file_uid = null;
var fileFolderPath = type === 'audio' ? audioFolderPath : videoFolderPath; const is_audio = type === 'audio';
const ext = is_audio ? '.mp3' : '.mp4';
var fileFolderPath = is_audio ? audioFolderPath : videoFolderPath;
if (is_audio) options.skip_audio_args = true;
// prepend with user if needed // prepend with user if needed
let multiUserMode = null; let multiUserMode = null;
@@ -1373,16 +1379,17 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) {
} }
}); });
video.on('end', function() { video.on('end', async function() {
let new_date = Date.now(); let new_date = Date.now();
let difference = (new_date - date)/1000; let difference = (new_date - date)/1000;
logger.debug(`Video download delay: ${difference} seconds.`); logger.debug(`Video download delay: ${difference} seconds.`);
download['timestamp_end'] = Date.now();
download['fileNames'] = [removeFileExtension(video_info._filename) + ext];
download['complete'] = true; download['complete'] = true;
updateDownloads(); updateDownloads();
// audio-only cleanup // audio-only cleanup
if (type === 'audio') { if (is_audio) {
// filename fix // filename fix
video_info['_filename'] = removeFileExtension(video_info['_filename']) + '.mp3'; video_info['_filename'] = removeFileExtension(video_info['_filename']) + '.mp3';
@@ -1393,6 +1400,12 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) {
} }
let success = NodeID3.write(tags, video_info._filename); let success = NodeID3.write(tags, video_info._filename);
if (!success) logger.error('Failed to apply ID3 tag to audio file ' + video_info._filename); if (!success) logger.error('Failed to apply ID3 tag to audio file ' + video_info._filename);
const possible_webm_path = removeFileExtension(video_info['_filename']) + '.webm';
const possible_mp4_path = removeFileExtension(video_info['_filename']) + '.mp4';
// check if audio file is webm
if (fs.existsSync(possible_webm_path)) await convertFileToMp3(possible_webm_path, video_info['_filename']);
else if (fs.existsSync(possible_mp4_path)) await convertFileToMp3(possible_mp4_path, video_info['_filename']);
} }
// registers file in DB // registers file in DB
@@ -1409,7 +1422,7 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) {
videopathEncoded = encodeURIComponent(removeFileExtension(base_file_name)); videopathEncoded = encodeURIComponent(removeFileExtension(base_file_name));
resolve({ resolve({
[(type === 'audio') ? 'audiopathEncoded' : 'videopathEncoded']: videopathEncoded, [is_audio ? 'audiopathEncoded' : 'videopathEncoded']: videopathEncoded,
file_names: /*is_playlist ? file_names :*/ null, // playlist support is not ready file_names: /*is_playlist ? file_names :*/ null, // playlist support is not ready
uid: file_uid uid: file_uid
}); });
@@ -1451,7 +1464,7 @@ async function generateArgs(url, type, options) {
var youtubePassword = options.youtubePassword; var youtubePassword = options.youtubePassword;
let downloadConfig = null; let downloadConfig = null;
let qualityPath = is_audio ? '-f bestaudio' :'-f best[ext=mp4]'; let qualityPath = (is_audio && !options.skip_audio_args) ? '-f bestaudio' :'-f best[ext=mp4]';
if (!is_audio && (url.includes('tiktok') || url.includes('pscp.tv'))) { if (!is_audio && (url.includes('tiktok') || url.includes('pscp.tv'))) {
// tiktok videos fail when using the default format // tiktok videos fail when using the default format
@@ -1470,12 +1483,12 @@ async function generateArgs(url, type, options) {
} }
if (customOutput) { if (customOutput) {
downloadConfig = ['-o', path.join(fileFolderPath, customOutput), qualityPath, '--write-info-json', '--print-json']; downloadConfig = ['-o', path.join(fileFolderPath, customOutput) + ".%(ext)s", qualityPath, '--write-info-json', '--print-json'];
} else { } else {
downloadConfig = ['-o', path.join(fileFolderPath, videopath + (is_audio ? '.%(ext)s' : '.mp4')), qualityPath, '--write-info-json', '--print-json']; downloadConfig = ['-o', path.join(fileFolderPath, videopath + (is_audio ? '.%(ext)s' : '.mp4')), qualityPath, '--write-info-json', '--print-json'];
} }
if (is_audio) { if (is_audio && !options.skip_audio_args) {
downloadConfig.push('-x'); downloadConfig.push('-x');
downloadConfig.push('--audio-format', 'mp3'); downloadConfig.push('--audio-format', 'mp3');
} }
@@ -1519,6 +1532,8 @@ async function generateArgs(url, type, options) {
} }
} }
// downloadConfig.map((arg) => `"${arg}"`);
console.log(downloadConfig.toString())
resolve(downloadConfig); resolve(downloadConfig);
}); });
} }
@@ -1550,6 +1565,23 @@ async function getUrlInfos(urls) {
}); });
} }
async function convertFileToMp3(input_file, output_file) {
logger.verbose(`Converting ${input_file} to ${output_file}...`);
return new Promise(resolve => {
ffmpeg(input_file).noVideo().toFormat('mp3')
.on('end', () => {
logger.verbose(`Conversion for '${output_file}' complete.`);
fs.unlinkSync(input_file)
resolve(true);
})
.on('error', (err) => {
logger.error('Failed to convert audio file to the correct format.');
logger.error(err);
resolve(false);
}).save(output_file);
});
}
function writeToBlacklist(type, line) { function writeToBlacklist(type, line) {
let blacklistPath = path.join(archivePath, (type === 'audio') ? 'blacklist_audio.txt' : 'blacklist_video.txt'); let blacklistPath = path.join(archivePath, (type === 'audio') ? 'blacklist_audio.txt' : 'blacklist_video.txt');
// adds newline to the beginning of the line // adds newline to the beginning of the line
@@ -1821,7 +1853,7 @@ app.post('/api/tomp3', optionalJwt, async function(req, res) {
} }
const is_playlist = url.includes('playlist'); const is_playlist = url.includes('playlist');
if (true || is_playlist) if (is_playlist || options.customQualityConfiguration )
result_obj = await downloadFileByURL_exec(url, 'audio', options, req.query.sessionID); result_obj = await downloadFileByURL_exec(url, 'audio', options, req.query.sessionID);
else else
result_obj = await downloadFileByURL_normal(url, 'audio', options, req.query.sessionID); result_obj = await downloadFileByURL_normal(url, 'audio', options, req.query.sessionID);

View File

@@ -35,6 +35,7 @@
"config": "^3.2.3", "config": "^3.2.3",
"exe": "^1.0.2", "exe": "^1.0.2",
"express": "^4.17.1", "express": "^4.17.1",
"fluent-ffmpeg": "^2.1.2",
"fs-extra": "^9.0.0", "fs-extra": "^9.0.0",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"lowdb": "^1.0.0", "lowdb": "^1.0.0",

View File

@@ -7,7 +7,7 @@
</h4> </h4>
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div *ngFor="let download of session_downloads.value | keyvalue; let i = index;" class="col-12 my-1"> <div *ngFor="let download of session_downloads.value | keyvalue: sort_downloads; let i = index;" class="col-12 my-1">
<mat-card *ngIf="download.value" class="mat-elevation-z3"> <mat-card *ngIf="download.value" class="mat-elevation-z3">
<app-download-item [download]="download.value" [queueNumber]="i+1" (cancelDownload)="clearDownload(session_downloads.key, download.value.uid)"></app-download-item> <app-download-item [download]="download.value" [queueNumber]="i+1" (cancelDownload)="clearDownload(session_downloads.key, download.value.uid)"></app-download-item>
</mat-card> </mat-card>

View File

@@ -1,4 +1,4 @@
import { Component, OnInit, ViewChildren, QueryList, ElementRef } from '@angular/core'; import { Component, OnInit, ViewChildren, QueryList, ElementRef, OnDestroy } from '@angular/core';
import { PostsService } from 'app/posts.services'; import { PostsService } from 'app/posts.services';
import { trigger, transition, animateChild, stagger, query, style, animate } from '@angular/animations'; import { trigger, transition, animateChild, stagger, query, style, animate } from '@angular/animations';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
@@ -32,20 +32,26 @@ import { Router } from '@angular/router';
]) ])
], ],
}) })
export class DownloadsComponent implements OnInit { export class DownloadsComponent implements OnInit, OnDestroy {
downloads_check_interval = 500; downloads_check_interval = 1000;
downloads = {}; downloads = {};
interval_id = null;
keys = Object.keys; keys = Object.keys;
valid_sessions_length = 0; valid_sessions_length = 0;
sort_downloads = (a, b) => {
const result = a.value.timestamp_start < b.value.timestamp_start;
return result;
}
constructor(public postsService: PostsService, private router: Router) { } constructor(public postsService: PostsService, private router: Router) { }
ngOnInit(): void { ngOnInit(): void {
this.getCurrentDownloads(); this.getCurrentDownloads();
setInterval(() => { this.interval_id = setInterval(() => {
this.getCurrentDownloads(); this.getCurrentDownloads();
}, this.downloads_check_interval); }, this.downloads_check_interval);
@@ -58,6 +64,10 @@ export class DownloadsComponent implements OnInit {
}); });
} }
ngOnDestroy() {
if (this.interval_id) { clearInterval(this.interval_id) }
}
getCurrentDownloads() { getCurrentDownloads() {
this.postsService.getCurrentDownloads().subscribe(res => { this.postsService.getCurrentDownloads().subscribe(res => {
if (res['downloads']) { if (res['downloads']) {

View File

@@ -12,10 +12,30 @@
<button style="margin-bottom: 2px;" (click)="cancelTheDownload()" mat-icon-button color="warn"><mat-icon fontSet="material-icons-outlined">cancel</mat-icon></button> <button style="margin-bottom: 2px;" (click)="cancelTheDownload()" mat-icon-button color="warn"><mat-icon fontSet="material-icons-outlined">cancel</mat-icon></button>
</mat-grid-tile> </mat-grid-tile>
</mat-grid-list> </mat-grid-list>
<mat-expansion-panel class="ignore-margin" *ngIf="download.error"> <mat-expansion-panel *ngIf="download.timestamp_start" class="ignore-margin">
<mat-expansion-panel-header> <mat-expansion-panel-header>
Error <div>
<ng-container i18n="Details">Details</ng-container>
</div>
<div style="width: 100%">
<div style="float: right">
<mat-panel-description>{{download.timestamp_start | date:'medium'}}</mat-panel-description>
</div>
</div>
</mat-expansion-panel-header> </mat-expansion-panel-header>
{{download.error}} <div *ngIf="download.error">
<strong>An error has occured:</strong>
<br/>
{{download.error}}
</div>
<div *ngIf="download.timestamp_start">
<strong>Download start: </strong>{{download.timestamp_start | date:'medium'}}
</div>
<div *ngIf="download.timestamp_end">
<strong>Download end: </strong> {{download.timestamp_end | date:'medium'}}
</div>
<div *ngIf="download.fileNames">
<strong>File path(s): </strong> {{download.fileNames.join(', ')}}
</div>
</mat-expansion-panel> </mat-expansion-panel>
</div> </div>

View File

@@ -16,6 +16,8 @@ export class DownloadItemComponent implements OnInit {
complete: false, complete: false,
url: 'http://youtube.com/watch?v=17848rufj', url: 'http://youtube.com/watch?v=17848rufj',
downloading: true, downloading: true,
timestamp_start: null,
timestamp_end: null,
is_playlist: false, is_playlist: false,
error: false error: false
}; };

View File

@@ -36,6 +36,8 @@ export interface Download {
error: boolean | string; error: boolean | string;
fileNames?: string[]; fileNames?: string[];
complete?: boolean; complete?: boolean;
timestamp_start?: number;
timestamp_end?: number;
} }
@Component({ @Component({