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 path = require('path');
var youtubedl = require('youtube-dl');
var ffmpeg = require('fluent-ffmpeg');
var compression = require('compression');
var https = require('https');
var express = require("express");
@@ -1287,6 +1288,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) {
}
download['complete'] = true;
download['fileNames'] = is_playlist ? file_names : [full_file_path]
updateDownloads();
var videopathEncoded = encodeURIComponent(file_names[0]);
@@ -1305,7 +1307,11 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) {
return new Promise(async resolve => {
var date = Date.now();
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
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 difference = (new_date - date)/1000;
logger.debug(`Video download delay: ${difference} seconds.`);
download['timestamp_end'] = Date.now();
download['fileNames'] = [removeFileExtension(video_info._filename) + ext];
download['complete'] = true;
updateDownloads();
// audio-only cleanup
if (type === 'audio') {
if (is_audio) {
// filename fix
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);
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
@@ -1409,7 +1422,7 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) {
videopathEncoded = encodeURIComponent(removeFileExtension(base_file_name));
resolve({
[(type === 'audio') ? 'audiopathEncoded' : 'videopathEncoded']: videopathEncoded,
[is_audio ? 'audiopathEncoded' : 'videopathEncoded']: videopathEncoded,
file_names: /*is_playlist ? file_names :*/ null, // playlist support is not ready
uid: file_uid
});
@@ -1451,7 +1464,7 @@ async function generateArgs(url, type, options) {
var youtubePassword = options.youtubePassword;
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'))) {
// tiktok videos fail when using the default format
@@ -1470,12 +1483,12 @@ async function generateArgs(url, type, options) {
}
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 {
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('--audio-format', 'mp3');
}
@@ -1519,6 +1532,8 @@ async function generateArgs(url, type, options) {
}
}
// downloadConfig.map((arg) => `"${arg}"`);
console.log(downloadConfig.toString())
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) {
let blacklistPath = path.join(archivePath, (type === 'audio') ? 'blacklist_audio.txt' : 'blacklist_video.txt');
// 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');
if (true || is_playlist)
if (is_playlist || options.customQualityConfiguration )
result_obj = await downloadFileByURL_exec(url, 'audio', options, req.query.sessionID);
else
result_obj = await downloadFileByURL_normal(url, 'audio', options, req.query.sessionID);

View File

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

View File

@@ -7,7 +7,7 @@
</h4>
<div class="container">
<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">
<app-download-item [download]="download.value" [queueNumber]="i+1" (cancelDownload)="clearDownload(session_downloads.key, download.value.uid)"></app-download-item>
</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 { trigger, transition, animateChild, stagger, query, style, animate } from '@angular/animations';
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 = {};
interval_id = null;
keys = Object.keys;
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) { }
ngOnInit(): void {
this.getCurrentDownloads();
setInterval(() => {
this.interval_id = setInterval(() => {
this.getCurrentDownloads();
}, this.downloads_check_interval);
@@ -58,6 +64,10 @@ export class DownloadsComponent implements OnInit {
});
}
ngOnDestroy() {
if (this.interval_id) { clearInterval(this.interval_id) }
}
getCurrentDownloads() {
this.postsService.getCurrentDownloads().subscribe(res => {
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>
</mat-grid-tile>
</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>
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>
{{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>
</div>

View File

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

View File

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