Added support for custom arguments and custom output patch

This commit is contained in:
Isaac Grynsztein
2020-02-27 03:27:57 -05:00
parent fc3691336d
commit b79d801c0f
11 changed files with 253 additions and 121 deletions

View File

@@ -413,6 +413,30 @@ async function deleteVideoFile(name) {
});
}
function recFindByExt(base,ext,files,result)
{
files = files || fs.readdirSync(base)
result = result || []
files.forEach(
function (file) {
var newbase = path.join(base,file)
if ( fs.statSync(newbase).isDirectory() )
{
result = recFindByExt(newbase,ext,fs.readdirSync(newbase),result)
}
else
{
if ( file.substr(-1*(ext.length+1)) == '.' + ext )
{
result.push(newbase)
}
}
}
)
return result
}
function getAudioInfos(fileNames) {
let result = [];
for (let i = 0; i < fileNames.length; i++) {
@@ -485,28 +509,40 @@ app.use(function(req, res, next) {
app.post('/tomp3', function(req, res) {
var url = req.body.url;
var date = Date.now();
var path = audioFolderPath;
var audiopath = '%(title)s';
var customQualityConfiguration = req.body.customQualityConfiguration;
var maxBitrate = req.body.maxBitrate;
var customArgs = req.body.customArgs;
var customOutput = req.body.customOutput;
let downloadConfig = ['-o', path + audiopath + ".mp3", '-x', '--audio-format', 'mp3', '--write-info-json', '--print-json']
let downloadConfig = null;
let qualityPath = '';
if (customQualityConfiguration) {
qualityPath = `-f ${customQualityConfiguration}`;
} else if (maxBitrate) {
if (!maxBitrate || maxBitrate === '') maxBitrate = '0';
qualityPath = `--audio-quality ${maxBitrate}`
}
if (customArgs) {
downloadConfig = [customArgs];
} else {
if (customOutput) {
downloadConfig = ['-o', audioFolderPath + customOutput + '.mp3', '-x', '--audio-format', 'mp3', '--write-info-json', '--print-json'];
} else {
downloadConfig = ['-o', audioFolderPath + audiopath + ".mp3", '-x', '--audio-format', 'mp3', '--write-info-json', '--print-json'];
}
if (qualityPath !== '') {
downloadConfig.splice(2, 0, qualityPath);
}
if (!useDefaultDownloadingAgent && customDownloadingAgent === 'aria2c') {
downloadConfig.splice(0, 0, '--external-downloader', 'aria2c');
if (customQualityConfiguration) {
qualityPath = `-f ${customQualityConfiguration}`;
} else if (maxBitrate) {
if (!maxBitrate || maxBitrate === '') maxBitrate = '0';
qualityPath = `--audio-quality ${maxBitrate}`
}
if (qualityPath !== '') {
downloadConfig.splice(2, 0, qualityPath);
}
if (!useDefaultDownloadingAgent && customDownloadingAgent === 'aria2c') {
downloadConfig.splice(0, 0, '--external-downloader', 'aria2c');
}
}
youtubedl.exec(url, downloadConfig, {}, function(err, output) {
@@ -534,12 +570,14 @@ app.post('/tomp3', function(req, res) {
continue;
}
var file_name = output_json['_filename'].replace(/^.*[\\\/]/, '');
var file_path = output_json['_filename'].substring(audioFolderPath.length, output_json['_filename'].length);
var alternate_file_path = file_path.substring(0, file_path.length-4);
var alternate_file_name = file_name.substring(0, file_name.length-4);
if (alternate_file_name) file_names.push(alternate_file_name);
if (alternate_file_path) file_names.push(alternate_file_path);
}
let is_playlist = file_names.length > 1;
if (!is_playlist) audiopath = file_names[0];
// if (!is_playlist) audiopath = file_names[0];
var audiopathEncoded = encodeURIComponent(file_names[0]);
res.send({
@@ -555,22 +593,35 @@ app.post('/tomp4', function(req, res) {
var date = Date.now();
var path = videoFolderPath;
var videopath = '%(title)s';
var customArgs = req.body.customArgs;
var customOutput = req.body.customOutput;
var selectedHeight = req.body.selectedHeight;
var customQualityConfiguration = req.body.customQualityConfiguration;
let downloadConfig = null;
let qualityPath = 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4';
if (customQualityConfiguration) {
qualityPath = customQualityConfiguration;
} else if (selectedHeight && selectedHeight !== '') {
qualityPath = `bestvideo[height=${selectedHeight}]+bestaudio/best[height=${selectedHeight}]`;
if (customArgs) {
downloadConfig = [customArgs];
} else {
if (customOutput) {
downloadConfig = ['-o', path + customOutput + ".mp4", '-f', qualityPath, '--write-info-json', '--print-json'];
} else {
downloadConfig = ['-o', path + videopath + ".mp4", '-f', qualityPath, '--write-info-json', '--print-json'];
}
if (customQualityConfiguration) {
qualityPath = customQualityConfiguration;
} else if (selectedHeight && selectedHeight !== '') {
qualityPath = `bestvideo[height=${selectedHeight}]+bestaudio/best[height=${selectedHeight}]`;
}
if (!useDefaultDownloadingAgent && customDownloadingAgent === 'aria2c') {
downloadConfig.splice(0, 0, '--external-downloader', 'aria2c');
}
}
let downloadConfig = ['-o', path + videopath + ".mp4", '-f', qualityPath, '--write-info-json', '--print-json']
if (!useDefaultDownloadingAgent && customDownloadingAgent === 'aria2c') {
downloadConfig.splice(0, 0, '--external-downloader', 'aria2c');
}
youtubedl.exec(url, downloadConfig, {}, function(err, output) {
if (debugMode) {
let new_date = Date.now();
@@ -606,7 +657,9 @@ app.post('/tomp4', function(req, res) {
}
}
var alternate_file_name = file_name.substring(0, file_name.length-4);
if (alternate_file_name) file_names.push(alternate_file_name);
var file_path = output_json['_filename'].substring(audioFolderPath.length, output_json['_filename'].length);
var alternate_file_path = file_path.substring(0, file_path.length-4);
if (alternate_file_name) file_names.push(alternate_file_path);
}
let is_playlist = file_names.length > 1;
@@ -668,31 +721,25 @@ app.post('/fileStatusMp4', function(req, res) {
app.post('/getMp3s', function(req, res) {
var mp3s = [];
var playlists = db.get('playlists.audio').value();
var fullpath = audioFolderPath;
var files = fs.readdirSync(audioFolderPath);
for (var i in files)
{
var nameLength = path.basename(files[i]).length;
var ext = path.basename(files[i]).substring(nameLength-4, nameLength);
if (ext == ".mp3")
var files = recFindByExt(audioFolderPath, 'mp3'); // fs.readdirSync(audioFolderPath);
for (let i = 0; i < files.length; i++) {
let file = files[i];
var file_path = file.substring(audioFolderPath.length, file.length);
var id = file_path.substring(0, file_path.length-4);
var jsonobj = getJSONMp3(id);
if (!jsonobj) continue;
var title = jsonobj.title;
if (title.length > 14) // edits title if it's too long
{
var jsonobj = getJSONMp3(path.basename(files[i]).substring(0, path.basename(files[i]).length-4));
if (!jsonobj) continue;
var id = path.basename(files[i]).substring(0, path.basename(files[i]).length-4);
var title = jsonobj.title;
if (title.length > 14) // edits title if it's too long
{
title = title.substring(0,12) + "...";
}
var thumbnail = jsonobj.thumbnail;
var duration = jsonobj.duration;
var isaudio = true;
var file = new File(id, title, thumbnail, isaudio, duration);
mp3s.push(file);
title = title.substring(0,12) + "...";
}
var thumbnail = jsonobj.thumbnail;
var duration = jsonobj.duration;
var isaudio = true;
var file_obj = new File(id, title, thumbnail, isaudio, duration);
mp3s.push(file_obj);
}
res.send({
@@ -707,30 +754,25 @@ app.post('/getMp4s', function(req, res) {
var mp4s = [];
var playlists = db.get('playlists.video').value();
var fullpath = videoFolderPath;
var files = fs.readdirSync(videoFolderPath);
for (var i in files)
{
var nameLength = path.basename(files[i]).length;
var ext = path.basename(files[i]).substring(nameLength-4, nameLength);
if (ext == ".mp4")
var files = recFindByExt(videoFolderPath, 'mp4');
for (let i = 0; i < files.length; i++) {
let file = files[i];
var file_path = file.substring(videoFolderPath.length, file.length);
var id = file_path.substring(0, file_path.length-4);
var jsonobj = getJSONMp4(id);
if (!jsonobj) continue;
var title = jsonobj.title;
if (title.length > 14) // edits title if it's too long
{
var jsonobj = getJSONMp4(path.basename(files[i]).substring(0, path.basename(files[i]).length-4));
if (!jsonobj) continue;
var id = path.basename(files[i]).substring(0, path.basename(files[i]).length-4);
var title = jsonobj.title;
if (title.length > 14) // edits title if it's too long
{
title = title.substring(0,12) + "...";
}
var thumbnail = jsonobj.thumbnail;
var duration = jsonobj.duration;
var isaudio = false;
var file = new File(id, title, thumbnail, isaudio, duration);
mp4s.push(file);
title = title.substring(0,12) + "...";
}
var thumbnail = jsonobj.thumbnail;
var duration = jsonobj.duration;
var isaudio = false;
var file_obj = new File(id, title, thumbnail, isaudio, duration);
mp4s.push(file_obj);
}
res.send({
@@ -882,46 +924,48 @@ app.post('/deleteFile', async (req, res) => {
app.get('/video/:id', function(req , res){
var head;
const path = "video/" + req.params.id + '.mp4';
const stat = fs.statSync(path)
const fileSize = stat.size
const range = req.headers.range
if (range) {
const parts = range.replace(/bytes=/, "").split("-")
const start = parseInt(parts[0], 10)
const end = parts[1]
? parseInt(parts[1], 10)
: fileSize-1
const chunksize = (end-start)+1
const file = fs.createReadStream(path, {start, end})
if (descriptors[req.params.id]) descriptors[req.params.id].push(file);
else descriptors[req.params.id] = [file];
file.on('close', function() {
let index = descriptors[req.params.id].indexOf(file);
descriptors[req.params.id].splice(index, 1);
if (debugMode) console.log('Successfully closed stream and removed file reference.');
});
head = {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunksize,
'Content-Type': 'video/mp4',
let id = decodeURI(req.params.id);
const path = "video/" + id + '.mp4';
const stat = fs.statSync(path)
const fileSize = stat.size
const range = req.headers.range
if (range) {
const parts = range.replace(/bytes=/, "").split("-")
const start = parseInt(parts[0], 10)
const end = parts[1]
? parseInt(parts[1], 10)
: fileSize-1
const chunksize = (end-start)+1
const file = fs.createReadStream(path, {start, end})
if (descriptors[id]) descriptors[id].push(file);
else descriptors[id] = [file];
file.on('close', function() {
let index = descriptors[id].indexOf(file);
descriptors[id].splice(index, 1);
if (debugMode) console.log('Successfully closed stream and removed file reference.');
});
head = {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunksize,
'Content-Type': 'video/mp4',
}
res.writeHead(206, head);
file.pipe(res);
} else {
head = {
'Content-Length': fileSize,
'Content-Type': 'video/mp4',
}
res.writeHead(200, head)
fs.createReadStream(path).pipe(res)
}
res.writeHead(206, head);
file.pipe(res);
} else {
head = {
'Content-Length': fileSize,
'Content-Type': 'video/mp4',
}
res.writeHead(200, head)
fs.createReadStream(path).pipe(res)
}
});
app.get('/audio/:id', function(req , res){
var head;
let path = "audio/" + req.params.id + '.mp3';
let id = decodeURI(req.params.id);
let path = "audio/" + id + '.mp3';
path = path.replace(/\"/g, '\'');
const stat = fs.statSync(path)
const fileSize = stat.size
@@ -934,11 +978,11 @@ app.get('/audio/:id', function(req , res){
: fileSize-1
const chunksize = (end-start)+1
const file = fs.createReadStream(path, {start, end});
if (descriptors[req.params.id]) descriptors[req.params.id].push(file);
else descriptors[req.params.id] = [file];
if (descriptors[id]) descriptors[id].push(file);
else descriptors[id] = [file];
file.on('close', function() {
let index = descriptors[req.params.id].indexOf(file);
descriptors[req.params.id].splice(index, 1);
let index = descriptors[id].indexOf(file);
descriptors[id].splice(index, 1);
if (debugMode) console.log('Successfully closed stream and removed file reference.');
});
head = {

View File

@@ -31,7 +31,8 @@
},
"Advanced": {
"use_default_downloading_agent": true,
"custom_downloading_agent": ""
"custom_downloading_agent": "",
"allow_advanced_download": false
}
}
}

View File

@@ -31,7 +31,8 @@
},
"Advanced": {
"use_default_downloading_agent": true,
"custom_downloading_agent": ""
"custom_downloading_agent": "",
"allow_advanced_download": false
}
}
}

View File

@@ -91,6 +91,10 @@ let CONFIG_ITEMS = {
'key': 'ytdl_custom_downloading_agent',
'path': 'YoutubeDLMaterial.Advanced.custom_downloading_agent'
},
'ytdl_allow_advanced_download': {
'key': 'ytdl_allow_advanced_download',
'path': 'YoutubeDLMaterial.Advanced.allow_advanced_download'
},
};
module.exports.CONFIG_ITEMS = CONFIG_ITEMS;

View File

@@ -24,8 +24,9 @@ services:
ytdl_allow_theme_change: 'true'
ytdl_use_default_downloading_agent: 'true'
ytdl_custom_downloading_agent: 'false'
write_ytdl_config: 'true'
ytdl_allow_advanced_download: 'false'
# do not touch this
write_ytdl_config: 'true'
ALLOW_CONFIG_MUTATIONS: 'true'
restart: always
ports:

View File

@@ -115,4 +115,10 @@ mat-form-field.mat-form-field {
.add-playlist-button {
float: right;
}
.advanced-input {
width: 100%;
margin-top: 20px;
margin-bottom: 20px;
}

View File

@@ -61,7 +61,36 @@
</mat-card-actions>
</mat-card>
</div>
<div *ngIf="downloads.length > 0 && !current_download" style="margin-top: 15px;" class="big demo-basic">
<div *ngIf="allowAdvancedDownload" class="big demo-basic">
<form style="margin-left: 20px; margin-right: 20px;">
<mat-expansion-panel class="big">
<mat-expansion-panel-header>
<mat-panel-title>
Advanced
</mat-panel-title>
</mat-expansion-panel-header>
<div class="container" style="padding-bottom: 20px;">
<div class="row">
<div class="col">
<mat-checkbox color="accent" [disabled]="current_download" (change)="customArgsEnabledChanged($event)" [(ngModel)]="customArgsEnabled" style="position: absolute; z-index: 999" [ngModelOptions]="{standalone: true}">Use custom args</mat-checkbox>
<mat-form-field color="accent" class="advanced-input">
<input [(ngModel)]="customArgs" [ngModelOptions]="{standalone: true}" [disabled]="!customArgsEnabled" matInput placeholder="Custom args">
<mat-hint>No need to include URL, just everything after.</mat-hint>
</mat-form-field>
</div>
<div class="col">
<mat-checkbox color="accent" [disabled]="current_download" (change)="customOutputEnabledChanged($event)" [(ngModel)]="customOutputEnabled" style="position: absolute; z-index: 999" [ngModelOptions]="{standalone: true}">Use custom output</mat-checkbox>
<mat-form-field color="accent" class="advanced-input">
<input [(ngModel)]="customOutput" [ngModelOptions]="{standalone: true}" [disabled]="!customOutputEnabled" matInput placeholder="Custom output">
<mat-hint><a target="_blank" href="https://github.com/ytdl-org/youtube-dl/blob/master/README.md#output-template">This link</a> will be helpful. Path is relative to the config download path.</mat-hint>
</mat-form-field>
</div>
</div>
</div>
</mat-expansion-panel>
</form>
</div>
<div *ngIf="multiDownloadMode && 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">

View File

@@ -47,6 +47,10 @@ export class MainComponent implements OnInit {
downloadingfile = false;
audioOnly: boolean;
multiDownloadMode = false;
customArgsEnabled = false;
customArgs = null;
customOutputEnabled = false;
customOutput = null;
urlError = false;
path = '';
url = '';
@@ -61,6 +65,7 @@ export class MainComponent implements OnInit {
baseStreamPath;
audioFolderPath;
videoFolderPath;
allowAdvancedDownload = false;
cachedAvailableFormats = {};
@@ -209,6 +214,7 @@ export class MainComponent implements OnInit {
result['YoutubeDLMaterial']['API']['youtube_API_key'];
this.youtubeAPIKey = this.youtubeSearchEnabled ? result['YoutubeDLMaterial']['API']['youtube_API_key'] : null;
this.allowQualitySelect = result['YoutubeDLMaterial']['Extra']['allow_quality_select'];
this.allowAdvancedDownload = result['YoutubeDLMaterial']['Advanced']['allow_advanced_download'];
this.postsService.path = backendUrl;
this.postsService.startPath = backendUrl;
@@ -223,6 +229,18 @@ export class MainComponent implements OnInit {
this.youtubeSearch.initializeAPI(this.youtubeAPIKey);
this.attachToInput();
}
// set final cache items
if (this.allowAdvancedDownload) {
if (localStorage.getItem('customArgsEnabled') !== null) {
this.customArgsEnabled = localStorage.getItem('customArgsEnabled') === 'true';
}
if (localStorage.getItem('customOutputEnabled') !== null) {
this.customOutputEnabled = localStorage.getItem('customOutputEnabled') === 'true';
}
}
}, error => {
console.log(error);
});
@@ -477,8 +495,12 @@ export class MainComponent implements OnInit {
customQualityConfiguration = audio_formats[this.selectedQuality]['format_id'];
}
}
const customArgs = (this.customArgsEnabled ? this.customArgs : null);
const customOutput = (this.customOutputEnabled ? this.customOutput : null);
this.postsService.makeMP3(this.url, (this.selectedQuality === '' ? null : this.selectedQuality),
customQualityConfiguration).subscribe(posts => {
customQualityConfiguration, customArgs, customOutput).subscribe(posts => {
// update download object
new_download.downloading = false;
new_download.percent_complete = 100;
@@ -516,8 +538,11 @@ export class MainComponent implements OnInit {
}
}
const customArgs = (this.customArgsEnabled ? this.customArgs : null);
const customOutput = (this.customOutputEnabled ? this.customOutput : null);
this.postsService.makeMP4(this.url, (this.selectedQuality === '' ? null : this.selectedQuality),
customQualityConfiguration).subscribe(posts => {
customQualityConfiguration, customArgs, customOutput).subscribe(posts => {
// update download object
new_download.downloading = false;
new_download.percent_complete = 100;
@@ -744,6 +769,20 @@ export class MainComponent implements OnInit {
localStorage.setItem('multiDownloadMode', new_val.checked.toString());
}
customArgsEnabledChanged(new_val) {
localStorage.setItem('customArgsEnabled', new_val.checked.toString());
if (new_val.checked === true && this.customOutputEnabled) {
this.customOutputEnabled = false;
}
}
customOutputEnabledChanged(new_val) {
localStorage.setItem('customOutputEnabled', new_val.checked.toString());
if (new_val.checked === true && this.customArgsEnabled) {
this.customArgsEnabled = false;
}
}
getAudioAndVideoFormats(formats): any[] {
const audio_formats = {};
const video_formats = {};

View File

@@ -76,7 +76,7 @@ export class PlayerComponent implements OnInit {
for (let i = 0; i < this.fileNames.length; i++) {
const fileName = this.fileNames[i];
const baseLocation = (this.type === 'audio') ? this.audioFolderPath : this.videoFolderPath;
const fullLocation = this.baseStreamPath + baseLocation + fileName; // + (this.type === 'audio' ? '.mp3' : '.mp4');
const fullLocation = this.baseStreamPath + baseLocation + encodeURI(fileName); // + (this.type === 'audio' ? '.mp3' : '.mp4');
const mediaObject: IMedia = {
title: fileName,
src: fullLocation,

View File

@@ -43,16 +43,22 @@ export class PostsService {
return this.http.get(this.startPath + 'audiofolder');
}
makeMP3(url: string, selectedQuality: string, customQualityConfiguration: string) {
// tslint:disable-next-line: max-line-length
makeMP3(url: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, customOutput: string = null) {
return this.http.post(this.path + 'tomp3', {url: url,
maxBitrate: selectedQuality,
customQualityConfiguration: customQualityConfiguration});
customQualityConfiguration: customQualityConfiguration,
customArgs: customArgs,
customOutput: customOutput});
}
makeMP4(url: string, selectedQuality: string, customQualityConfiguration: string) {
// tslint:disable-next-line: max-line-length
makeMP4(url: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, customOutput: string = null) {
return this.http.post(this.path + 'tomp4', {url: url,
selectedHeight: selectedQuality,
customQualityConfiguration: customQualityConfiguration});
customQualityConfiguration: customQualityConfiguration,
customArgs: customArgs,
customOutput: customOutput});
}
getFileStatusMp3(name: string) {

View File

@@ -31,7 +31,8 @@
},
"Advanced": {
"use_default_downloading_agent": true,
"custom_downloading_agent": ""
"custom_downloading_agent": "",
"allow_advanced_download": true
}
}
}