mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-04-14 18:41:29 +03:00
Merge pull request #57 from Tzahi12345/current-downloads-refactor
Downloads system refactor
This commit is contained in:
787
backend/app.js
787
backend/app.js
@@ -77,6 +77,7 @@ db.defaults(
|
|||||||
video: []
|
video: []
|
||||||
},
|
},
|
||||||
configWriteFlag: false,
|
configWriteFlag: false,
|
||||||
|
downloads: {},
|
||||||
subscriptions: [],
|
subscriptions: [],
|
||||||
pin_md5: '',
|
pin_md5: '',
|
||||||
files_to_db_migration_complete: false
|
files_to_db_migration_complete: false
|
||||||
@@ -101,6 +102,10 @@ var archivePath = path.join(__dirname, 'appdata', 'archives');
|
|||||||
var options = null; // encryption options
|
var options = null; // encryption options
|
||||||
var url_domain = null;
|
var url_domain = null;
|
||||||
var updaterStatus = null;
|
var updaterStatus = null;
|
||||||
|
var last_downloads_check = null;
|
||||||
|
var downloads_check_interval = 1000;
|
||||||
|
|
||||||
|
var timestamp_server_start = Date.now();
|
||||||
|
|
||||||
if (debugMode) logger.info('YTDL-Material in debug mode!');
|
if (debugMode) logger.info('YTDL-Material in debug mode!');
|
||||||
|
|
||||||
@@ -141,6 +146,7 @@ if (writeConfigMode) {
|
|||||||
loadConfig();
|
loadConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var downloads = {};
|
||||||
var descriptors = {};
|
var descriptors = {};
|
||||||
|
|
||||||
app.use(bodyParser.urlencoded({ extended: false }));
|
app.use(bodyParser.urlencoded({ extended: false }));
|
||||||
@@ -233,7 +239,6 @@ async function startServer() {
|
|||||||
logger.info(`YoutubeDL-Material ${CONSTS['CURRENT_VERSION']} started on PORT ${backendPort}`);
|
logger.info(`YoutubeDL-Material ${CONSTS['CURRENT_VERSION']} started on PORT ${backendPort}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function restartServer() {
|
async function restartServer() {
|
||||||
@@ -546,6 +551,9 @@ async function loadConfig() {
|
|||||||
// check migrations
|
// check migrations
|
||||||
await checkMigrations();
|
await checkMigrations();
|
||||||
|
|
||||||
|
// load in previous downloads
|
||||||
|
downloads = db.get('downloads').value();
|
||||||
|
|
||||||
// start the server here
|
// start the server here
|
||||||
startServer();
|
startServer();
|
||||||
|
|
||||||
@@ -997,9 +1005,18 @@ function registerFileDB(full_file_path, type) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add additional info
|
||||||
file_object['uid'] = uuid();
|
file_object['uid'] = uuid();
|
||||||
|
file_object['registered'] = Date.now();
|
||||||
path_object = path.parse(file_object['path']);
|
path_object = path.parse(file_object['path']);
|
||||||
file_object['path'] = path.format(path_object);
|
file_object['path'] = path.format(path_object);
|
||||||
|
|
||||||
|
// remove existing video if overwriting
|
||||||
|
db.get(`files.${type}`)
|
||||||
|
.remove({
|
||||||
|
path: file_object['path']
|
||||||
|
}).write();
|
||||||
|
|
||||||
db.get(`files.${type}`)
|
db.get(`files.${type}`)
|
||||||
.push(file_object)
|
.push(file_object)
|
||||||
.write();
|
.write();
|
||||||
@@ -1013,6 +1030,7 @@ function generateFileObject(id, type) {
|
|||||||
}
|
}
|
||||||
const ext = (type === 'audio') ? '.mp3' : '.mp4'
|
const ext = (type === 'audio') ? '.mp3' : '.mp4'
|
||||||
const file_path = getTrueFileName(jsonobj['_filename'], type); // path.join(type === 'audio' ? audioFolderPath : videoFolderPath, id + ext);
|
const file_path = getTrueFileName(jsonobj['_filename'], type); // path.join(type === 'audio' ? audioFolderPath : videoFolderPath, id + ext);
|
||||||
|
// console.
|
||||||
var stats = fs.statSync(path.join(__dirname, file_path));
|
var stats = fs.statSync(path.join(__dirname, file_path));
|
||||||
|
|
||||||
var title = jsonobj.title;
|
var title = jsonobj.title;
|
||||||
@@ -1080,6 +1098,332 @@ function getVideoInfos(fileNames) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// downloads
|
||||||
|
|
||||||
|
async function downloadFileByURL_exec(url, type, options, sessionID = null) {
|
||||||
|
return new Promise(async resolve => {
|
||||||
|
var date = Date.now();
|
||||||
|
|
||||||
|
// audio / video specific vars
|
||||||
|
var is_audio = type === 'audio';
|
||||||
|
var ext = is_audio ? '.mp3' : '.mp4';
|
||||||
|
var fileFolderPath = type === 'audio' ? audioFolderPath : videoFolderPath;
|
||||||
|
|
||||||
|
const downloadConfig = await generateArgs(url, type, options);
|
||||||
|
|
||||||
|
// adds download to download helper
|
||||||
|
const download_uid = uuid();
|
||||||
|
const session = sessionID ? sessionID : 'undeclared';
|
||||||
|
if (!downloads[session]) downloads[session] = {};
|
||||||
|
downloads[session][download_uid] = {
|
||||||
|
uid: download_uid,
|
||||||
|
ui_uid: options.ui_uid,
|
||||||
|
downloading: true,
|
||||||
|
complete: false,
|
||||||
|
url: url,
|
||||||
|
type: type,
|
||||||
|
percent_complete: 0,
|
||||||
|
is_playlist: url.includes('playlist'),
|
||||||
|
timestamp_start: Date.now()
|
||||||
|
};
|
||||||
|
const download = downloads[session][download_uid];
|
||||||
|
updateDownloads();
|
||||||
|
|
||||||
|
youtubedl.exec(url, downloadConfig, {}, function(err, output) {
|
||||||
|
download['downloading'] = false;
|
||||||
|
download['timestamp_end'] = Date.now();
|
||||||
|
var file_uid = null;
|
||||||
|
let new_date = Date.now();
|
||||||
|
let difference = (new_date - date)/1000;
|
||||||
|
logger.debug(`${is_audio ? 'Audio' : 'Video'} download delay: ${difference} seconds.`);
|
||||||
|
if (err) {
|
||||||
|
logger.error(err.stderr);
|
||||||
|
|
||||||
|
download['error'] = err.stderr;
|
||||||
|
updateDownloads();
|
||||||
|
resolve(false);
|
||||||
|
throw err;
|
||||||
|
} else if (output) {
|
||||||
|
if (output.length === 0 || output[0].length === 0) {
|
||||||
|
download['error'] = 'No output. Check if video already exists in your archive.';
|
||||||
|
updateDownloads();
|
||||||
|
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var file_names = [];
|
||||||
|
for (let i = 0; i < output.length; i++) {
|
||||||
|
let output_json = null;
|
||||||
|
try {
|
||||||
|
output_json = JSON.parse(output[i]);
|
||||||
|
} catch(e) {
|
||||||
|
output_json = null;
|
||||||
|
}
|
||||||
|
var modified_file_name = output_json ? output_json['title'] : null;
|
||||||
|
if (!output_json) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get filepath with no extension
|
||||||
|
const filepath_no_extension = removeFileExtension(output_json['_filename']);
|
||||||
|
|
||||||
|
var full_file_path = filepath_no_extension + ext;
|
||||||
|
var file_name = filepath_no_extension.substring(fileFolderPath.length, filepath_no_extension.length);
|
||||||
|
|
||||||
|
// renames file if necessary due to bug
|
||||||
|
if (!fs.existsSync(output_json['_filename'] && fs.existsSync(output_json['_filename'] + '.webm'))) {
|
||||||
|
try {
|
||||||
|
fs.renameSync(output_json['_filename'] + '.webm', output_json['_filename']);
|
||||||
|
logger.info('Renamed ' + file_name + '.webm to ' + file_name);
|
||||||
|
} catch(e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'audio') {
|
||||||
|
let tags = {
|
||||||
|
title: output_json['title'],
|
||||||
|
artist: output_json['artist'] ? output_json['artist'] : output_json['uploader']
|
||||||
|
}
|
||||||
|
let success = NodeID3.write(tags, output_json['_filename']);
|
||||||
|
if (!success) logger.error('Failed to apply ID3 tag to audio file ' + output_json['_filename']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// registers file in DB
|
||||||
|
file_uid = registerFileDB(full_file_path.substring(fileFolderPath.length, full_file_path.length), type);
|
||||||
|
|
||||||
|
if (file_name) file_names.push(file_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_playlist = file_names.length > 1;
|
||||||
|
|
||||||
|
if (options.merged_string) {
|
||||||
|
let current_merged_archive = fs.readFileSync(fileFolderPath + 'merged.txt', 'utf8');
|
||||||
|
let diff = current_merged_archive.replace(options.merged_string, '');
|
||||||
|
const archive_path = path.join(archivePath, `archive_${type}.txt`);
|
||||||
|
fs.appendFileSync(archive_path, diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
download['complete'] = true;
|
||||||
|
updateDownloads();
|
||||||
|
|
||||||
|
var videopathEncoded = encodeURIComponent(file_names[0]);
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
[(type === 'audio') ? 'audiopathEncoded' : 'videopathEncoded']: videopathEncoded,
|
||||||
|
file_names: is_playlist ? file_names : null,
|
||||||
|
uid: file_uid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 downloadConfig = await generateArgs(url, type, options);
|
||||||
|
|
||||||
|
// adds download to download helper
|
||||||
|
const download_uid = uuid();
|
||||||
|
const session = sessionID ? sessionID : 'undeclared';
|
||||||
|
if (!downloads[session]) downloads[session] = {};
|
||||||
|
downloads[session][download_uid] = {
|
||||||
|
uid: download_uid,
|
||||||
|
ui_uid: options.ui_uid,
|
||||||
|
downloading: true,
|
||||||
|
complete: false,
|
||||||
|
url: url,
|
||||||
|
type: type,
|
||||||
|
percent_complete: 0,
|
||||||
|
is_playlist: url.includes('playlist'),
|
||||||
|
timestamp_start: Date.now()
|
||||||
|
};
|
||||||
|
const download = downloads[session][download_uid];
|
||||||
|
updateDownloads();
|
||||||
|
|
||||||
|
const video = youtubedl(url,
|
||||||
|
// Optional arguments passed to youtube-dl.
|
||||||
|
downloadConfig,
|
||||||
|
// Additional options can be given for calling `child_process.execFile()`.
|
||||||
|
{ cwd: __dirname });
|
||||||
|
|
||||||
|
let video_info = null;
|
||||||
|
let file_size = 0;
|
||||||
|
|
||||||
|
// Will be called when the download starts.
|
||||||
|
video.on('info', function(info) {
|
||||||
|
video_info = info;
|
||||||
|
file_size = video_info.size;
|
||||||
|
fs.writeJSONSync(removeFileExtension(video_info._filename) + '.info.json', video_info);
|
||||||
|
video.pipe(fs.createWriteStream(video_info._filename, { flags: 'w' }))
|
||||||
|
});
|
||||||
|
// Will be called if download was already completed and there is nothing more to download.
|
||||||
|
video.on('complete', function complete(info) {
|
||||||
|
'use strict'
|
||||||
|
logger.info('file ' + info._filename + ' already downloaded.')
|
||||||
|
})
|
||||||
|
|
||||||
|
let download_pos = 0;
|
||||||
|
video.on('data', function data(chunk) {
|
||||||
|
download_pos += chunk.length
|
||||||
|
// `size` should not be 0 here.
|
||||||
|
if (file_size) {
|
||||||
|
let percent = (download_pos / file_size * 100).toFixed(2)
|
||||||
|
download['percent_complete'] = percent;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
video.on('end', function() {
|
||||||
|
let new_date = Date.now();
|
||||||
|
let difference = (new_date - date)/1000;
|
||||||
|
logger.debug(`Video download delay: ${difference} seconds.`);
|
||||||
|
|
||||||
|
download['complete'] = true;
|
||||||
|
updateDownloads();
|
||||||
|
|
||||||
|
// audio-only cleanup
|
||||||
|
if (type === 'audio') {
|
||||||
|
// filename fix
|
||||||
|
video_info['_filename'] = removeFileExtension(video_info['_filename']) + '.mp3';
|
||||||
|
|
||||||
|
// ID3 tagging
|
||||||
|
let tags = {
|
||||||
|
title: video_info['title'],
|
||||||
|
artist: video_info['artist'] ? video_info['artist'] : video_info['uploader']
|
||||||
|
}
|
||||||
|
let success = NodeID3.write(tags, video_info._filename);
|
||||||
|
if (!success) logger.error('Failed to apply ID3 tag to audio file ' + video_info._filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
// registers file in DB
|
||||||
|
const base_file_name = video_info._filename.substring(fileFolderPath.length, video_info._filename.length);
|
||||||
|
file_uid = registerFileDB(base_file_name, type);
|
||||||
|
|
||||||
|
if (options.merged_string) {
|
||||||
|
let current_merged_archive = fs.readFileSync(fileFolderPath + 'merged.txt', 'utf8');
|
||||||
|
let diff = current_merged_archive.replace(options.merged_string, '');
|
||||||
|
const archive_path = path.join(archivePath, 'archive_video.txt');
|
||||||
|
fs.appendFileSync(archive_path, diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
videopathEncoded = encodeURIComponent(removeFileExtension(base_file_name));
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
[(type === 'audio') ? 'audiopathEncoded' : 'videopathEncoded']: videopathEncoded,
|
||||||
|
file_names: /*is_playlist ? file_names :*/ null, // playlist support is not ready
|
||||||
|
uid: file_uid
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
video.on('error', function error(err) {
|
||||||
|
logger.error(err);
|
||||||
|
|
||||||
|
download[error] = err;
|
||||||
|
updateDownloads();
|
||||||
|
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateArgs(url, type, options) {
|
||||||
|
return new Promise(async resolve => {
|
||||||
|
var videopath = '%(title)s';
|
||||||
|
var globalArgs = config_api.getConfigItem('ytdl_custom_args');
|
||||||
|
var is_audio = type === 'audio';
|
||||||
|
|
||||||
|
var fileFolderPath = is_audio ? audioFolderPath : videoFolderPath;
|
||||||
|
|
||||||
|
var customArgs = options.customArgs;
|
||||||
|
var customOutput = options.customOutput;
|
||||||
|
var customQualityConfiguration = options.customQualityConfiguration;
|
||||||
|
|
||||||
|
// video-specific args
|
||||||
|
var selectedHeight = options.selectedHeight;
|
||||||
|
|
||||||
|
// audio-specific args
|
||||||
|
var maxBitrate = options.maxBitrate;
|
||||||
|
|
||||||
|
var youtubeUsername = options.youtubeUsername;
|
||||||
|
var youtubePassword = options.youtubePassword;
|
||||||
|
|
||||||
|
let downloadConfig = null;
|
||||||
|
let qualityPath = is_audio ? '-f bestaudio' :'-f best[ext=mp4]';
|
||||||
|
|
||||||
|
if (!is_audio && (url.includes('tiktok') || url.includes('pscp.tv'))) {
|
||||||
|
// tiktok videos fail when using the default format
|
||||||
|
qualityPath = '-f best';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customArgs) {
|
||||||
|
downloadConfig = customArgs.split(' ');
|
||||||
|
} else {
|
||||||
|
if (customQualityConfiguration) {
|
||||||
|
qualityPath = customQualityConfiguration;
|
||||||
|
} else if (selectedHeight && selectedHeight !== '' && !is_audio) {
|
||||||
|
qualityPath = `-f bestvideo[height=${selectedHeight}]+bestaudio/best[height=${selectedHeight}]`;
|
||||||
|
} else if (maxBitrate && is_audio) {
|
||||||
|
qualityPath = `--audio-quality ${maxBitrate}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customOutput) {
|
||||||
|
downloadConfig = ['-o', fileFolderPath + customOutput + "", qualityPath, '--write-info-json', '--print-json'];
|
||||||
|
} else {
|
||||||
|
downloadConfig = ['-o', fileFolderPath + videopath + (is_audio ? '.%(ext)s' : '.mp4'), qualityPath, '--write-info-json', '--print-json'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_audio) {
|
||||||
|
downloadConfig.push('-x');
|
||||||
|
downloadConfig.push('--audio-format', 'mp3');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (youtubeUsername && youtubePassword) {
|
||||||
|
downloadConfig.push('--username', youtubeUsername, '--password', youtubePassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!useDefaultDownloadingAgent && customDownloadingAgent) {
|
||||||
|
downloadConfig.splice(0, 0, '--external-downloader', customDownloadingAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
|
||||||
|
if (useYoutubeDLArchive) {
|
||||||
|
const archive_path = path.join(archivePath, `archive_${type}.txt`);
|
||||||
|
// create archive file if it doesn't exist
|
||||||
|
if (!fs.existsSync(archive_path)) {
|
||||||
|
fs.closeSync(fs.openSync(archive_path, 'w'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let blacklist_path = path.join(archivePath, `blacklist_${type}.txt`);
|
||||||
|
// create blacklist file if it doesn't exist
|
||||||
|
if (!fs.existsSync(blacklist_path)) {
|
||||||
|
fs.closeSync(fs.openSync(blacklist_path, 'w'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let merged_path = fileFolderPath + 'merged.txt';
|
||||||
|
fs.ensureFileSync(merged_path);
|
||||||
|
// merges blacklist and regular archive
|
||||||
|
let inputPathList = [archive_path, blacklist_path];
|
||||||
|
let status = await mergeFiles(inputPathList, merged_path);
|
||||||
|
|
||||||
|
options.merged_string = fs.readFileSync(merged_path, "utf8");
|
||||||
|
|
||||||
|
downloadConfig.push('--download-archive', merged_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalArgs && globalArgs !== '') {
|
||||||
|
// adds global args
|
||||||
|
downloadConfig = downloadConfig.concat(globalArgs.split(' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
resolve(downloadConfig);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// currently only works for single urls
|
// currently only works for single urls
|
||||||
async function getUrlInfos(urls) {
|
async function getUrlInfos(urls) {
|
||||||
let startDate = Date.now();
|
let startDate = Date.now();
|
||||||
@@ -1116,6 +1460,57 @@ function writeToBlacklist(type, line) {
|
|||||||
fs.appendFileSync(blacklistPath, line);
|
fs.appendFileSync(blacklistPath, line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// download management functions
|
||||||
|
|
||||||
|
function updateDownloads() {
|
||||||
|
db.assign({downloads: downloads}).write();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
function checkDownloads() {
|
||||||
|
for (let [session_id, session_downloads] of Object.entries(downloads)) {
|
||||||
|
for (let [download_uid, download_obj] of Object.entries(session_downloads)) {
|
||||||
|
if (download_obj && !download_obj['complete'] && !download_obj['error']
|
||||||
|
&& download_obj.timestamp_start > timestamp_server_start) {
|
||||||
|
// download is still running (presumably)
|
||||||
|
download_obj.percent_complete = getDownloadPercent(download_obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getDownloadPercent(download_obj) {
|
||||||
|
if (!download_obj.final_size) {
|
||||||
|
if (fs.existsSync(download_obj.expected_json_path)) {
|
||||||
|
const file_json = JSON.parse(fs.readFileSync(download_obj.expected_json_path, 'utf8'));
|
||||||
|
let calculated_filesize = null;
|
||||||
|
if (file_json['format_id']) {
|
||||||
|
calculated_filesize = 0;
|
||||||
|
const formats_used = file_json['format_id'].split('+');
|
||||||
|
for (let i = 0; i < file_json['formats'].length; i++) {
|
||||||
|
if (formats_used.includes(file_json['formats'][i]['format_id'])) {
|
||||||
|
calculated_filesize += file_json['formats'][i]['filesize'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
download_obj.final_size = calculated_filesize;
|
||||||
|
} else {
|
||||||
|
console.log('could not find json file');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fs.existsSync(download_obj.expected_path)) {
|
||||||
|
const stats = fs.statSync(download_obj.expected_path);
|
||||||
|
const size = stats.size;
|
||||||
|
return (size / download_obj.final_size)*100;
|
||||||
|
} else {
|
||||||
|
console.log('could not find file');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// youtube-dl functions
|
||||||
|
|
||||||
async function startYoutubeDL() {
|
async function startYoutubeDL() {
|
||||||
// auto update youtube-dl
|
// auto update youtube-dl
|
||||||
await autoUpdateYoutubeDL();
|
await autoUpdateYoutubeDL();
|
||||||
@@ -1287,297 +1682,55 @@ app.get('/api/using-encryption', function(req, res) {
|
|||||||
|
|
||||||
app.post('/api/tomp3', async function(req, res) {
|
app.post('/api/tomp3', async function(req, res) {
|
||||||
var url = req.body.url;
|
var url = req.body.url;
|
||||||
var date = Date.now();
|
var options = {
|
||||||
var audiopath = '%(title)s';
|
customArgs: req.body.customArgs,
|
||||||
|
customOutput: req.body.customOutput,
|
||||||
var customQualityConfiguration = req.body.customQualityConfiguration;
|
maxBitrate: req.body.maxBitrate,
|
||||||
var maxBitrate = req.body.maxBitrate;
|
customQualityConfiguration: req.body.customQualityConfiguration,
|
||||||
var globalArgs = config_api.getConfigItem('ytdl_custom_args');
|
youtubeUsername: req.body.youtubeUsername,
|
||||||
var customArgs = req.body.customArgs;
|
youtubePassword: req.body.youtubePassword,
|
||||||
var customOutput = req.body.customOutput;
|
ui_uid: req.body.ui_uid
|
||||||
var youtubeUsername = req.body.youtubeUsername;
|
|
||||||
var youtubePassword = req.body.youtubePassword;
|
|
||||||
|
|
||||||
let downloadConfig = null;
|
|
||||||
let qualityPath = '-f bestaudio';
|
|
||||||
|
|
||||||
let merged_path = null;
|
|
||||||
let merged_string = null;
|
|
||||||
|
|
||||||
if (customArgs) {
|
|
||||||
downloadConfig = customArgs.split(' ');
|
|
||||||
} else {
|
|
||||||
if (customQualityConfiguration) {
|
|
||||||
qualityPath = `-f ${customQualityConfiguration}`;
|
|
||||||
} else if (maxBitrate) {
|
|
||||||
if (!maxBitrate || maxBitrate === '') maxBitrate = '0';
|
|
||||||
qualityPath = `--audio-quality ${maxBitrate}`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customOutput) {
|
|
||||||
downloadConfig = ['-x', '--audio-format', 'mp3', '-o', audioFolderPath + customOutput + '.%(ext)s', '--write-info-json', '--print-json'];
|
|
||||||
} else {
|
|
||||||
downloadConfig = ['-x', '--audio-format', 'mp3', '-o', audioFolderPath + audiopath + ".%(ext)s", '--write-info-json', '--print-json'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (youtubeUsername && youtubePassword) {
|
|
||||||
downloadConfig.push('--username', youtubeUsername, '--password', youtubePassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (qualityPath !== '') {
|
|
||||||
downloadConfig.splice(3, 0, qualityPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!useDefaultDownloadingAgent && customDownloadingAgent) {
|
|
||||||
downloadConfig.splice(0, 0, '--external-downloader', customDownloadingAgent);
|
|
||||||
}
|
|
||||||
|
|
||||||
let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
|
|
||||||
if (useYoutubeDLArchive) {
|
|
||||||
const archive_path = path.join(archivePath, 'archive_audio.txt');
|
|
||||||
// create archive file if it doesn't exist
|
|
||||||
if (!fs.existsSync(archive_path)) {
|
|
||||||
fs.closeSync(fs.openSync(archive_path, 'w'));
|
|
||||||
}
|
|
||||||
|
|
||||||
let blacklist_path = path.join(archivePath, 'blacklist_audio.txt');
|
|
||||||
// create blacklist file if it doesn't exist
|
|
||||||
if (!fs.existsSync(blacklist_path)) {
|
|
||||||
fs.closeSync(fs.openSync(blacklist_path, 'w'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// creates merged folder
|
|
||||||
merged_path = audioFolderPath + `merged_${uuid()}.txt`;
|
|
||||||
// merges blacklist and regular archive
|
|
||||||
let inputPathList = [archive_path, blacklist_path];
|
|
||||||
let status = await mergeFiles(inputPathList, merged_path);
|
|
||||||
|
|
||||||
merged_string = fs.readFileSync(merged_path, "utf8");
|
|
||||||
|
|
||||||
downloadConfig.push('--download-archive', merged_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (globalArgs && globalArgs !== '') {
|
|
||||||
// adds global args
|
|
||||||
downloadConfig = downloadConfig.concat(globalArgs.split(' '));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
youtubedl.exec(url, downloadConfig, {}, function(err, output) {
|
const is_playlist = url.includes('playlist');
|
||||||
var uid = null;
|
if (true || is_playlist)
|
||||||
let new_date = Date.now();
|
result_obj = await downloadFileByURL_exec(url, 'audio', options, req.query.sessionID);
|
||||||
let difference = (new_date - date)/1000;
|
else
|
||||||
logger.debug(`Audio download delay: ${difference} seconds.`);
|
result_obj = await downloadFileByURL_normal(url, 'audio', options, req.query.sessionID);
|
||||||
if (err) {
|
if (result_obj) {
|
||||||
audiopath = "-1";
|
res.send(result_obj);
|
||||||
logger.error(err.stderr);
|
} else {
|
||||||
res.sendStatus(500);
|
res.sendStatus(500);
|
||||||
throw err;
|
}
|
||||||
} else if (output) {
|
|
||||||
var file_names = [];
|
res.end("yes");
|
||||||
if (output.length === 0 || output[0].length === 0) {
|
|
||||||
res.sendStatus(500);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (let i = 0; i < output.length; i++) {
|
|
||||||
let output_json = null;
|
|
||||||
try {
|
|
||||||
output_json = JSON.parse(output[i]);
|
|
||||||
} catch(e) {
|
|
||||||
output_json = null;
|
|
||||||
}
|
|
||||||
if (!output_json) {
|
|
||||||
// if invalid, continue onto the next
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filepath_no_extension = removeFileExtension(output_json['_filename']);
|
|
||||||
|
|
||||||
var full_file_path = filepath_no_extension + '.mp3';
|
|
||||||
var file_name = filepath_no_extension.substring(audioFolderPath.length, filepath_no_extension.length);
|
|
||||||
if (fs.existsSync(full_file_path)) {
|
|
||||||
let tags = {
|
|
||||||
title: output_json['title'],
|
|
||||||
artist: output_json['artist'] ? output_json['artist'] : output_json['uploader']
|
|
||||||
}
|
|
||||||
// NodeID3.create(tags, function(frame) { })
|
|
||||||
let success = NodeID3.write(tags, full_file_path);
|
|
||||||
if (!success) logger.error('Failed to apply ID3 tag to audio file ' + full_file_path);
|
|
||||||
|
|
||||||
// registers file in DB
|
|
||||||
uid = registerFileDB(full_file_path.substring(audioFolderPath.length, full_file_path.length), 'audio');
|
|
||||||
} else {
|
|
||||||
logger.error('Download failed: Output mp3 does not exist');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file_name) file_names.push(file_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_playlist = file_names.length > 1;
|
|
||||||
|
|
||||||
if (merged_string !== null) {
|
|
||||||
let current_merged_archive = fs.readFileSync(merged_path, 'utf8');
|
|
||||||
let diff = current_merged_archive.replace(merged_string, '');
|
|
||||||
const archive_path = path.join(archivePath, 'archive_audio.txt');
|
|
||||||
fs.appendFileSync(archive_path, diff);
|
|
||||||
fs.unlinkSync(merged_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
var audiopathEncoded = encodeURIComponent(file_names[0]);
|
|
||||||
res.send({
|
|
||||||
audiopathEncoded: audiopathEncoded,
|
|
||||||
file_names: is_playlist ? file_names : null,
|
|
||||||
uid: uid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/tomp4', async function(req, res) {
|
app.post('/api/tomp4', async function(req, res) {
|
||||||
var url = req.body.url;
|
var url = req.body.url;
|
||||||
var date = Date.now();
|
var options = {
|
||||||
var videopath = '%(title)s';
|
customArgs: req.body.customArgs,
|
||||||
var globalArgs = config_api.getConfigItem('ytdl_custom_args');
|
customOutput: req.body.customOutput,
|
||||||
var customArgs = req.body.customArgs;
|
selectedHeight: req.body.selectedHeight,
|
||||||
var customOutput = req.body.customOutput;
|
customQualityConfiguration: req.body.customQualityConfiguration,
|
||||||
|
youtubeUsername: req.body.youtubeUsername,
|
||||||
var selectedHeight = req.body.selectedHeight;
|
youtubePassword: req.body.youtubePassword,
|
||||||
var customQualityConfiguration = req.body.customQualityConfiguration;
|
ui_uid: req.body.ui_uid
|
||||||
var youtubeUsername = req.body.youtubeUsername;
|
|
||||||
var youtubePassword = req.body.youtubePassword;
|
|
||||||
|
|
||||||
let merged_string = null;
|
|
||||||
|
|
||||||
let downloadConfig = null;
|
|
||||||
let qualityPath = 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4';
|
|
||||||
|
|
||||||
if (url.includes('tiktok') || url.includes('pscp.tv')) {
|
|
||||||
// tiktok videos fail when using the default format
|
|
||||||
qualityPath = 'best';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customArgs) {
|
|
||||||
downloadConfig = customArgs.split(' ');
|
|
||||||
} else {
|
|
||||||
if (customQualityConfiguration) {
|
|
||||||
qualityPath = customQualityConfiguration;
|
|
||||||
} else if (selectedHeight && selectedHeight !== '') {
|
|
||||||
qualityPath = `bestvideo[height=${selectedHeight}]+bestaudio/best[height=${selectedHeight}]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customOutput) {
|
|
||||||
downloadConfig = ['-o', videoFolderPath + customOutput + ".mp4", '-f', qualityPath, '--write-info-json', '--print-json'];
|
|
||||||
} else {
|
|
||||||
downloadConfig = ['-o', videoFolderPath + videopath + ".mp4", '-f', qualityPath, '--write-info-json', '--print-json'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (youtubeUsername && youtubePassword) {
|
|
||||||
downloadConfig.push('--username', youtubeUsername, '--password', youtubePassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!useDefaultDownloadingAgent && customDownloadingAgent) {
|
const is_playlist = url.includes('playlist');
|
||||||
downloadConfig.splice(0, 0, '--external-downloader', customDownloadingAgent);
|
let result_obj = null;
|
||||||
}
|
if (is_playlist)
|
||||||
|
result_obj = await downloadFileByURL_exec(url, 'video', options, req.query.sessionID);
|
||||||
let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
|
else
|
||||||
if (useYoutubeDLArchive) {
|
result_obj = await downloadFileByURL_normal(url, 'video', options, req.query.sessionID);
|
||||||
const archive_path = path.join(archivePath, 'archive_video.txt');
|
if (result_obj) {
|
||||||
// create archive file if it doesn't exist
|
res.send(result_obj);
|
||||||
if (!fs.existsSync(archive_path)) {
|
} else {
|
||||||
fs.closeSync(fs.openSync(archive_path, 'w'));
|
res.sendStatus(500);
|
||||||
}
|
|
||||||
|
|
||||||
let blacklist_path = path.join(archivePath, 'blacklist_video.txt');
|
|
||||||
// create blacklist file if it doesn't exist
|
|
||||||
if (!fs.existsSync(blacklist_path)) {
|
|
||||||
fs.closeSync(fs.openSync(blacklist_path, 'w'));
|
|
||||||
}
|
|
||||||
|
|
||||||
let merged_path = videoFolderPath + 'merged.txt';
|
|
||||||
// merges blacklist and regular archive
|
|
||||||
let inputPathList = [archive_path, blacklist_path];
|
|
||||||
let status = await mergeFiles(inputPathList, merged_path);
|
|
||||||
|
|
||||||
merged_string = fs.readFileSync(merged_path, "utf8");
|
|
||||||
|
|
||||||
downloadConfig.push('--download-archive', merged_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (globalArgs && globalArgs !== '') {
|
|
||||||
// adds global args
|
|
||||||
downloadConfig = downloadConfig.concat(globalArgs.split(' '));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
youtubedl.exec(url, downloadConfig, {}, function(err, output) {
|
res.end("yes");
|
||||||
var uid = null;
|
|
||||||
let new_date = Date.now();
|
|
||||||
let difference = (new_date - date)/1000;
|
|
||||||
logger.debug(`Video download delay: ${difference} seconds.`);
|
|
||||||
if (err) {
|
|
||||||
videopath = "-1";
|
|
||||||
logger.error(err.stderr);
|
|
||||||
res.sendStatus(500);
|
|
||||||
throw err;
|
|
||||||
} else if (output) {
|
|
||||||
if (output.length === 0 || output[0].length === 0) {
|
|
||||||
res.sendStatus(500);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var file_names = [];
|
|
||||||
for (let i = 0; i < output.length; i++) {
|
|
||||||
let output_json = null;
|
|
||||||
try {
|
|
||||||
output_json = JSON.parse(output[i]);
|
|
||||||
} catch(e) {
|
|
||||||
output_json = null;
|
|
||||||
}
|
|
||||||
var modified_file_name = output_json ? output_json['title'] : null;
|
|
||||||
if (!output_json) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get filepath with no extension
|
|
||||||
const filepath_no_extension = removeFileExtension(output_json['_filename']);
|
|
||||||
|
|
||||||
var full_file_path = filepath_no_extension + '.mp4';
|
|
||||||
var file_name = filepath_no_extension.substring(audioFolderPath.length, filepath_no_extension.length);
|
|
||||||
|
|
||||||
// renames file if necessary due to bug
|
|
||||||
if (!fs.existsSync(output_json['_filename'] && fs.existsSync(output_json['_filename'] + '.webm'))) {
|
|
||||||
try {
|
|
||||||
fs.renameSync(output_json['_filename'] + '.webm', output_json['_filename']);
|
|
||||||
logger.info('Renamed ' + file_name + '.webm to ' + file_name);
|
|
||||||
} catch(e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// registers file in DB
|
|
||||||
uid = registerFileDB(full_file_path.substring(videoFolderPath.length, full_file_path.length), 'video');
|
|
||||||
|
|
||||||
if (file_name) file_names.push(file_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_playlist = file_names.length > 1;
|
|
||||||
if (!is_playlist) audiopath = file_names[0];
|
|
||||||
|
|
||||||
if (merged_string !== null) {
|
|
||||||
let current_merged_archive = fs.readFileSync(videoFolderPath + 'merged.txt', 'utf8');
|
|
||||||
let diff = current_merged_archive.replace(merged_string, '');
|
|
||||||
const archive_path = path.join(archivePath, 'archive_video.txt');
|
|
||||||
fs.appendFileSync(archive_path, diff);
|
|
||||||
}
|
|
||||||
|
|
||||||
var videopathEncoded = encodeURIComponent(file_names[0]);
|
|
||||||
res.send({
|
|
||||||
videopathEncoded: videopathEncoded,
|
|
||||||
file_names: is_playlist ? file_names : null,
|
|
||||||
uid: uid
|
|
||||||
});
|
|
||||||
res.end("yes");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// gets the status of the mp3 file that's being downloaded
|
// gets the status of the mp3 file that's being downloaded
|
||||||
@@ -2282,6 +2435,74 @@ app.get('/api/audio/:id', function(req , res){
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Downloads management
|
||||||
|
|
||||||
|
app.get('/api/downloads', async (req, res) => {
|
||||||
|
/*
|
||||||
|
if (!last_downloads_check || Date.now() - last_downloads_check > downloads_check_interval) {
|
||||||
|
last_downloads_check = Date.now();
|
||||||
|
updateDownloads();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
res.send({downloads: downloads});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/download', async (req, res) => {
|
||||||
|
var session_id = req.body.session_id;
|
||||||
|
var download_id = req.body.download_id;
|
||||||
|
let found_download = null;
|
||||||
|
|
||||||
|
// find download
|
||||||
|
if (downloads[session_id] && Object.keys(downloads[session_id])) {
|
||||||
|
let session_downloads = Object.values(downloads[session_id]);
|
||||||
|
for (let i = 0; i < session_downloads.length; i++) {
|
||||||
|
let session_download = session_downloads[i];
|
||||||
|
if (session_download && session_download['ui_uid'] === download_id) {
|
||||||
|
found_download = session_download;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found_download) {
|
||||||
|
res.send({download: found_download});
|
||||||
|
} else {
|
||||||
|
res.send({download: null});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/clearDownloads', async (req, res) => {
|
||||||
|
let success = false;
|
||||||
|
var delete_all = req.body.delete_all;
|
||||||
|
if (!req.body.session_id) req.body.session_id = 'undeclared';
|
||||||
|
var session_id = req.body.session_id;
|
||||||
|
var download_id = req.body.download_id;
|
||||||
|
if (delete_all) {
|
||||||
|
// delete all downloads
|
||||||
|
downloads = {};
|
||||||
|
success = true;
|
||||||
|
} else if (download_id) {
|
||||||
|
// delete just 1 download
|
||||||
|
if (downloads[session_id][download_id]) {
|
||||||
|
delete downloads[session_id][download_id];
|
||||||
|
success = true;
|
||||||
|
} else if (!downloads[session_id]) {
|
||||||
|
logger.error(`Session ${session_id} has no downloads.`)
|
||||||
|
} else if (!downloads[session_id][download_id]) {
|
||||||
|
logger.error(`Download '${download_id}' for session '${session_id}' could not be found`);
|
||||||
|
}
|
||||||
|
} else if (session_id) {
|
||||||
|
// delete a session's downloads
|
||||||
|
if (downloads[session_id]) {
|
||||||
|
delete downloads[session_id];
|
||||||
|
success = true;
|
||||||
|
} else {
|
||||||
|
logger.error(`Session ${session_id} has no downloads.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateDownloads();
|
||||||
|
res.send({success: success, downloads: downloads});
|
||||||
|
});
|
||||||
|
|
||||||
app.post('/api/getVideoInfos', async (req, res) => {
|
app.post('/api/getVideoInfos', async (req, res) => {
|
||||||
let fileNames = req.body.fileNames;
|
let fileNames = req.body.fileNames;
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import { MainComponent } from './main/main.component';
|
|||||||
import { PlayerComponent } from './player/player.component';
|
import { PlayerComponent } from './player/player.component';
|
||||||
import { SubscriptionsComponent } from './subscriptions/subscriptions.component';
|
import { SubscriptionsComponent } from './subscriptions/subscriptions.component';
|
||||||
import { SubscriptionComponent } from './subscription/subscription/subscription.component';
|
import { SubscriptionComponent } from './subscription/subscription/subscription.component';
|
||||||
|
import { DownloadsComponent } from './components/downloads/downloads.component';
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: 'home', component: MainComponent },
|
{ path: 'home', component: MainComponent },
|
||||||
{ path: 'player', component: PlayerComponent},
|
{ path: 'player', component: PlayerComponent},
|
||||||
{ path: 'subscriptions', component: SubscriptionsComponent },
|
{ path: 'subscriptions', component: SubscriptionsComponent },
|
||||||
{ path: 'subscription', component: SubscriptionComponent },
|
{ path: 'subscription', component: SubscriptionComponent },
|
||||||
|
{ path: 'downloads', component: DownloadsComponent },
|
||||||
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<mat-toolbar color="primary" class="sticky-toolbar top-toolbar">
|
<mat-toolbar color="primary" class="sticky-toolbar top-toolbar">
|
||||||
<div class="flex-row" width="100%" height="100%">
|
<div class="flex-row" width="100%" height="100%">
|
||||||
<div class="flex-column" style="text-align: left; margin-top: 1px;">
|
<div class="flex-column" style="text-align: left; margin-top: 1px;">
|
||||||
<button #hamburgerMenu style="outline: none" *ngIf="router.url.split(';')[0] !== '/player' && allowSubscriptions" mat-icon-button aria-label="Toggle side navigation" (click)="toggleSidenav()"><mat-icon>menu</mat-icon></button>
|
<button #hamburgerMenu style="outline: none" *ngIf="router.url.split(';')[0] !== '/player'" mat-icon-button aria-label="Toggle side navigation" (click)="toggleSidenav()"><mat-icon>menu</mat-icon></button>
|
||||||
<button (click)="goBack()" *ngIf="router.url.split(';')[0] === '/player'" mat-icon-button><mat-icon>arrow_back</mat-icon></button>
|
<button (click)="goBack()" *ngIf="router.url.split(';')[0] === '/player'" mat-icon-button><mat-icon>arrow_back</mat-icon></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-column" style="text-align: center; margin-top: 5px;">
|
<div class="flex-column" style="text-align: center; margin-top: 5px;">
|
||||||
@@ -37,7 +37,8 @@
|
|||||||
<mat-sidenav #sidenav>
|
<mat-sidenav #sidenav>
|
||||||
<mat-nav-list>
|
<mat-nav-list>
|
||||||
<a mat-list-item (click)="sidenav.close()" routerLink='/home'><ng-container i18n="Navigation menu Home Page title">Home</ng-container></a>
|
<a mat-list-item (click)="sidenav.close()" routerLink='/home'><ng-container i18n="Navigation menu Home Page title">Home</ng-container></a>
|
||||||
<a mat-list-item (click)="sidenav.close()" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a>
|
<a *ngIf="allowSubscriptions" mat-list-item (click)="sidenav.close()" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a>
|
||||||
|
<a mat-list-item (click)="sidenav.close()" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</ng-container></a>
|
||||||
</mat-nav-list>
|
</mat-nav-list>
|
||||||
</mat-sidenav>
|
</mat-sidenav>
|
||||||
<mat-sidenav-content [style.background]="postsService.theme ? postsService.theme.background_color : null">
|
<mat-sidenav-content [style.background]="postsService.theme ? postsService.theme.background_color : null">
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ import { ArgModifierDialogComponent, HighlightPipe } from './dialogs/arg-modifie
|
|||||||
import { UpdaterComponent } from './updater/updater.component';
|
import { UpdaterComponent } from './updater/updater.component';
|
||||||
import { UpdateProgressDialogComponent } from './dialogs/update-progress-dialog/update-progress-dialog.component';
|
import { UpdateProgressDialogComponent } from './dialogs/update-progress-dialog/update-progress-dialog.component';
|
||||||
import { ShareMediaDialogComponent } from './dialogs/share-media-dialog/share-media-dialog.component';
|
import { ShareMediaDialogComponent } from './dialogs/share-media-dialog/share-media-dialog.component';
|
||||||
|
import { DownloadsComponent } from './components/downloads/downloads.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>) {
|
||||||
@@ -85,7 +86,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
|||||||
HighlightPipe,
|
HighlightPipe,
|
||||||
UpdaterComponent,
|
UpdaterComponent,
|
||||||
UpdateProgressDialogComponent,
|
UpdateProgressDialogComponent,
|
||||||
ShareMediaDialogComponent
|
ShareMediaDialogComponent,
|
||||||
|
DownloadsComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
|||||||
24
src/app/components/downloads/downloads.component.html
Normal file
24
src/app/components/downloads/downloads.component.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<div style="padding: 20px;">
|
||||||
|
<div *ngFor="let session_downloads of downloads | keyvalue">
|
||||||
|
<ng-container *ngIf="keys(session_downloads.value).length > 0">
|
||||||
|
<mat-card style="padding-bottom: 30px; margin-bottom: 15px;">
|
||||||
|
<h4 style="text-align: center;"><ng-container i18n="Session ID">Session ID:</ng-container> {{session_downloads.key}}
|
||||||
|
<span *ngIf="session_downloads.key === postsService.session_id"> <ng-container i18n="Current session">(current)</ng-container></span>
|
||||||
|
</h4>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div *ngFor="let download of session_downloads.value | keyvalue; 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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-card>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="downloads && !downloadsValid()">
|
||||||
|
<h4 style="text-align: center;" i18n="No downloads label">No downloads available!</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
25
src/app/components/downloads/downloads.component.spec.ts
Normal file
25
src/app/components/downloads/downloads.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { DownloadsComponent } from './downloads.component';
|
||||||
|
|
||||||
|
describe('DownloadsComponent', () => {
|
||||||
|
let component: DownloadsComponent;
|
||||||
|
let fixture: ComponentFixture<DownloadsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ DownloadsComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DownloadsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
131
src/app/components/downloads/downloads.component.ts
Normal file
131
src/app/components/downloads/downloads.component.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import { Component, OnInit, ViewChildren, QueryList, ElementRef } from '@angular/core';
|
||||||
|
import { PostsService } from 'app/posts.services';
|
||||||
|
import { trigger, transition, animateChild, stagger, query, style, animate } from '@angular/animations';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-downloads',
|
||||||
|
templateUrl: './downloads.component.html',
|
||||||
|
styleUrls: ['./downloads.component.scss'],
|
||||||
|
animations: [
|
||||||
|
// nice stagger effect when showing existing elements
|
||||||
|
trigger('list', [
|
||||||
|
transition(':enter', [
|
||||||
|
// child animation selector + stagger
|
||||||
|
query('@items',
|
||||||
|
stagger(100, animateChild()), { optional: true }
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
trigger('items', [
|
||||||
|
// cubic-bezier for a tiny bouncing feel
|
||||||
|
transition(':enter', [
|
||||||
|
style({ transform: 'scale(0.5)', opacity: 0 }),
|
||||||
|
animate('500ms cubic-bezier(.8,-0.6,0.2,1.5)',
|
||||||
|
style({ transform: 'scale(1)', opacity: 1 }))
|
||||||
|
]),
|
||||||
|
transition(':leave', [
|
||||||
|
style({ transform: 'scale(1)', opacity: 1, height: '*' }),
|
||||||
|
animate('1s cubic-bezier(.8,-0.6,0.2,1.5)',
|
||||||
|
style({ transform: 'scale(0.5)', opacity: 0, height: '0px', margin: '0px' }))
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class DownloadsComponent implements OnInit {
|
||||||
|
|
||||||
|
downloads_check_interval = 500;
|
||||||
|
downloads = {};
|
||||||
|
|
||||||
|
keys = Object.keys;
|
||||||
|
|
||||||
|
valid_sessions_length = 0;
|
||||||
|
|
||||||
|
constructor(public postsService: PostsService) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.getCurrentDownloads();
|
||||||
|
setInterval(() => {
|
||||||
|
this.getCurrentDownloads();
|
||||||
|
}, this.downloads_check_interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentDownloads() {
|
||||||
|
this.postsService.getCurrentDownloads().subscribe(res => {
|
||||||
|
if (res['downloads']) {
|
||||||
|
this.assignNewValues(res['downloads']);
|
||||||
|
} else {
|
||||||
|
// failed to get downloads
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clearDownload(session_id, download_uid) {
|
||||||
|
this.postsService.clearDownloads(false, session_id, download_uid).subscribe(res => {
|
||||||
|
if (res['success']) {
|
||||||
|
this.downloads = res['downloads'];
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clearDownloads(session_id) {
|
||||||
|
this.postsService.clearDownloads(false, session_id).subscribe(res => {
|
||||||
|
if (res['success']) {
|
||||||
|
this.downloads = res['downloads'];
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAllDownloads() {
|
||||||
|
this.postsService.clearDownloads(true).subscribe(res => {
|
||||||
|
if (res['success']) {
|
||||||
|
this.downloads = res['downloads'];
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
assignNewValues(new_downloads_by_session) {
|
||||||
|
const session_keys = Object.keys(new_downloads_by_session);
|
||||||
|
for (let i = 0; i < session_keys.length; i++) {
|
||||||
|
const session_id = session_keys[i];
|
||||||
|
const session_downloads_by_id = new_downloads_by_session[session_id];
|
||||||
|
const session_download_ids = Object.keys(session_downloads_by_id);
|
||||||
|
|
||||||
|
if (!this.downloads[session_id]) {
|
||||||
|
this.downloads[session_id] = session_downloads_by_id;
|
||||||
|
} else {
|
||||||
|
for (let j = 0; j < session_download_ids.length; j++) {
|
||||||
|
const download_id = session_download_ids[j];
|
||||||
|
const download = new_downloads_by_session[session_id][download_id]
|
||||||
|
if (!this.downloads[session_id][download_id]) {
|
||||||
|
this.downloads[session_id][download_id] = download;
|
||||||
|
} else {
|
||||||
|
const download_to_update = this.downloads[session_id][download_id];
|
||||||
|
download_to_update['percent_complete'] = download['percent_complete'];
|
||||||
|
download_to_update['complete'] = download['complete'];
|
||||||
|
download_to_update['timestamp_end'] = download['timestamp_end'];
|
||||||
|
download_to_update['downloading'] = download['downloading'];
|
||||||
|
download_to_update['error'] = download['error'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadsValid() {
|
||||||
|
let valid = false;
|
||||||
|
const keys = this.keys(this.downloads);
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
const key = keys[i];
|
||||||
|
const value = this.downloads[key];
|
||||||
|
if (this.keys(value).length > 0) {
|
||||||
|
valid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,16 +1,21 @@
|
|||||||
<div>
|
<div>
|
||||||
<mat-grid-list [rowHeight]="50" [cols]="24">
|
<mat-grid-list [rowHeight]="50" [cols]="24">
|
||||||
<mat-grid-tile [colspan]="2">
|
<mat-grid-tile [colspan]="7">
|
||||||
<h5 style="display: inline-block; margin-right: 5px; position: relative; top: 5px;">{{queueNumber}}.</h5>
|
<div style="display: inline-block; text-align: center; width: 100%;"><span class="shorten"><ng-container i18n="Download ID">ID:</ng-container> {{url_id ? url_id : download.uid}}</span></div>
|
||||||
</mat-grid-tile>
|
|
||||||
<mat-grid-tile [colspan]="6">
|
|
||||||
<div style="display: inline-block; text-align: center;"><ng-container i18n="Download ID">ID:</ng-container> {{url_id}}</div>
|
|
||||||
</mat-grid-tile>
|
</mat-grid-tile>
|
||||||
<mat-grid-tile [colspan]="13">
|
<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-progress-bar [value]="(download.complete || download.error) ? 100 : download.percent_complete" [mode]="(!download.complete && download.percent_complete === 0 && !download.error) ? 'indeterminate' : 'determinate'"></mat-progress-bar>
|
||||||
|
<mat-icon *ngIf="download.complete" style="margin-left: 25px; cursor: default" matTooltip="The download is complete" matTooltip-i18n>done</mat-icon>
|
||||||
|
<mat-icon *ngIf="download.error" style="margin-left: 25px; cursor: default" matTooltip="An error has occurred" matTooltip-i18n>error</mat-icon>
|
||||||
</mat-grid-tile>
|
</mat-grid-tile>
|
||||||
<mat-grid-tile [colspan]="3">
|
<mat-grid-tile [colspan]="4">
|
||||||
<button (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-header>
|
||||||
|
Error
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
{{download.error}}
|
||||||
|
</mat-expansion-panel>
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
.shorten {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-expansion-panel:not([class*='mat-elevation-z']) {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ignore-margin {
|
||||||
|
margin-left: -15px;
|
||||||
|
margin-right: -15px;
|
||||||
|
margin-bottom: -15px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ export class DownloadItemComponent implements OnInit {
|
|||||||
uid: null,
|
uid: null,
|
||||||
type: 'audio',
|
type: 'audio',
|
||||||
percent_complete: 0,
|
percent_complete: 0,
|
||||||
|
complete: false,
|
||||||
url: 'http://youtube.com/watch?v=17848rufj',
|
url: 'http://youtube.com/watch?v=17848rufj',
|
||||||
downloading: true,
|
downloading: true,
|
||||||
is_playlist: false
|
is_playlist: false,
|
||||||
|
error: false
|
||||||
};
|
};
|
||||||
@Output() cancelDownload = new EventEmitter<Download>();
|
@Output() cancelDownload = new EventEmitter<Download>();
|
||||||
|
|
||||||
|
|||||||
@@ -170,11 +170,11 @@
|
|||||||
<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">
|
||||||
<div [ngClass]="(determinateProgress && percentDownloaded === 100)?'make-room-for-spinner':'equal-sizes'" style="display: inline-block; width: 100%; padding-left: 20px" *ngIf="determinateProgress;else indeterminateprogress">
|
<div [ngClass]="(percentDownloaded > 15 && percentDownloaded === 100)?'make-room-for-spinner':'equal-sizes'" style="display: inline-block; width: 100%; padding-left: 20px" *ngIf="current_download.percent_complete && current_download.percent_complete > 15;else indeterminateprogress">
|
||||||
<mat-progress-bar mode="determinate" value="{{percentDownloaded}}"></mat-progress-bar>
|
<mat-progress-bar style="border-radius: 5px;" mode="determinate" value="{{percentDownloaded}}"></mat-progress-bar>
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="determinateProgress && percentDownloaded === 100" class="spinner">
|
<div *ngIf="percentDownloaded === 100" class="spinner">
|
||||||
<mat-spinner [diameter]="25"></mat-spinner>
|
<mat-spinner [diameter]="25"></mat-spinner>
|
||||||
</div>
|
</div>
|
||||||
<ng-template #indeterminateprogress>
|
<ng-template #indeterminateprogress>
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ export interface Download {
|
|||||||
percent_complete: number;
|
percent_complete: number;
|
||||||
downloading: boolean;
|
downloading: boolean;
|
||||||
is_playlist: boolean;
|
is_playlist: boolean;
|
||||||
|
error: boolean | string;
|
||||||
fileNames?: string[];
|
fileNames?: string[];
|
||||||
|
complete?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -207,7 +209,8 @@ export class MainComponent implements OnInit {
|
|||||||
percent_complete: 0,
|
percent_complete: 0,
|
||||||
url: 'http://youtube.com/watch?v=17848rufj',
|
url: 'http://youtube.com/watch?v=17848rufj',
|
||||||
downloading: true,
|
downloading: true,
|
||||||
is_playlist: false
|
is_playlist: false,
|
||||||
|
error: false
|
||||||
};
|
};
|
||||||
|
|
||||||
simulatedOutput = '';
|
simulatedOutput = '';
|
||||||
@@ -281,6 +284,13 @@ export class MainComponent implements OnInit {
|
|||||||
if (youtubeUsername && youtubeUsername !== 'null') { this.youtubeUsername = youtubeUsername };
|
if (youtubeUsername && youtubeUsername !== 'null') { this.youtubeUsername = youtubeUsername };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get downloads routine
|
||||||
|
setInterval(() => {
|
||||||
|
if (this.current_download) {
|
||||||
|
this.getCurrentDownload();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}, error => {
|
}, error => {
|
||||||
@@ -571,7 +581,8 @@ export class MainComponent implements OnInit {
|
|||||||
percent_complete: 0,
|
percent_complete: 0,
|
||||||
url: this.url,
|
url: this.url,
|
||||||
downloading: true,
|
downloading: true,
|
||||||
is_playlist: this.url.includes('playlist')
|
is_playlist: this.url.includes('playlist'),
|
||||||
|
error: false
|
||||||
};
|
};
|
||||||
this.downloads.push(new_download);
|
this.downloads.push(new_download);
|
||||||
if (!this.current_download && !this.multiDownloadMode) { this.current_download = new_download };
|
if (!this.current_download && !this.multiDownloadMode) { this.current_download = new_download };
|
||||||
@@ -583,7 +594,7 @@ export class MainComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.postsService.makeMP3(this.url, (this.selectedQuality === '' ? null : this.selectedQuality),
|
this.postsService.makeMP3(this.url, (this.selectedQuality === '' ? null : this.selectedQuality),
|
||||||
customQualityConfiguration, customArgs, customOutput, youtubeUsername, youtubePassword).subscribe(posts => {
|
customQualityConfiguration, customArgs, customOutput, youtubeUsername, youtubePassword, new_download.uid).subscribe(posts => {
|
||||||
// update download object
|
// update download object
|
||||||
new_download.downloading = false;
|
new_download.downloading = false;
|
||||||
new_download.percent_complete = 100;
|
new_download.percent_complete = 100;
|
||||||
@@ -591,6 +602,8 @@ export class MainComponent implements OnInit {
|
|||||||
const is_playlist = !!(posts['file_names']);
|
const is_playlist = !!(posts['file_names']);
|
||||||
this.path = is_playlist ? posts['file_names'] : posts['audiopathEncoded'];
|
this.path = is_playlist ? posts['file_names'] : posts['audiopathEncoded'];
|
||||||
|
|
||||||
|
this.current_download = null;
|
||||||
|
|
||||||
if (this.path !== '-1') {
|
if (this.path !== '-1') {
|
||||||
this.downloadHelperMp3(this.path, posts['uid'], is_playlist, false, new_download);
|
this.downloadHelperMp3(this.path, posts['uid'], is_playlist, false, new_download);
|
||||||
}
|
}
|
||||||
@@ -613,7 +626,8 @@ export class MainComponent implements OnInit {
|
|||||||
percent_complete: 0,
|
percent_complete: 0,
|
||||||
url: this.url,
|
url: this.url,
|
||||||
downloading: true,
|
downloading: true,
|
||||||
is_playlist: this.url.includes('playlist')
|
is_playlist: this.url.includes('playlist'),
|
||||||
|
error: false
|
||||||
};
|
};
|
||||||
this.downloads.push(new_download);
|
this.downloads.push(new_download);
|
||||||
if (!this.current_download && !this.multiDownloadMode) { this.current_download = new_download };
|
if (!this.current_download && !this.multiDownloadMode) { this.current_download = new_download };
|
||||||
@@ -622,7 +636,7 @@ export class MainComponent implements OnInit {
|
|||||||
const customQualityConfiguration = this.getSelectedVideoFormat();
|
const customQualityConfiguration = this.getSelectedVideoFormat();
|
||||||
|
|
||||||
this.postsService.makeMP4(this.url, (this.selectedQuality === '' ? null : this.selectedQuality),
|
this.postsService.makeMP4(this.url, (this.selectedQuality === '' ? null : this.selectedQuality),
|
||||||
customQualityConfiguration, customArgs, customOutput, youtubeUsername, youtubePassword).subscribe(posts => {
|
customQualityConfiguration, customArgs, customOutput, youtubeUsername, youtubePassword, new_download.uid).subscribe(posts => {
|
||||||
// update download object
|
// update download object
|
||||||
new_download.downloading = false;
|
new_download.downloading = false;
|
||||||
new_download.percent_complete = 100;
|
new_download.percent_complete = 100;
|
||||||
@@ -630,6 +644,8 @@ export class MainComponent implements OnInit {
|
|||||||
const is_playlist = !!(posts['file_names']);
|
const is_playlist = !!(posts['file_names']);
|
||||||
this.path = is_playlist ? posts['file_names'] : posts['videopathEncoded'];
|
this.path = is_playlist ? posts['file_names'] : posts['videopathEncoded'];
|
||||||
|
|
||||||
|
this.current_download = null;
|
||||||
|
|
||||||
if (this.path !== '-1') {
|
if (this.path !== '-1') {
|
||||||
this.downloadHelperMp4(this.path, posts['uid'], is_playlist, false, new_download);
|
this.downloadHelperMp4(this.path, posts['uid'], is_playlist, false, new_download);
|
||||||
}
|
}
|
||||||
@@ -1119,4 +1135,21 @@ export class MainComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCurrentDownload() {
|
||||||
|
this.postsService.getCurrentDownload(this.postsService.session_id,
|
||||||
|
this.current_download['ui_uid'] ? this.current_download['ui_uid'] : this.current_download['uid']).subscribe(res => {
|
||||||
|
const ui_uid = this.current_download['ui_uid'] ? this.current_download['ui_uid'] : this.current_download['uid'];
|
||||||
|
if (res['download']) {
|
||||||
|
console.log('got new download');
|
||||||
|
if (ui_uid === res['download']['ui_uid']) {
|
||||||
|
this.current_download = res['download'];
|
||||||
|
this.percentDownloaded = this.current_download.percent_complete;
|
||||||
|
console.log(this.percentDownloaded);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('failed to get new download');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div *ngIf="playlist.length === 1">
|
<div *ngIf="playlist.length === 1">
|
||||||
<button class="save-button" color="primary" (click)="downloadFile()" [disabled]="downloading" mat-fab><mat-icon class="save-icon">save</mat-icon><mat-spinner *ngIf="downloading" class="spinner" [diameter]="50"></mat-spinner></button>
|
<button class="save-button" color="primary" (click)="downloadFile()" [disabled]="downloading" mat-fab><mat-icon class="save-icon">save</mat-icon><mat-spinner *ngIf="downloading" class="spinner" [diameter]="50"></mat-spinner></button>
|
||||||
<button *ngIf="!is_shared && type !== 'subscription'" class="share-button" color="primary" (click)="openShareDialog()" mat-fab><mat-icon class="save-icon">share</mat-icon></button>
|
<button *ngIf="!is_shared && uid && uid !== 'false' && type !== 'subscription'" class="share-button" color="primary" (click)="openShareDialog()" mat-fab><mat-icon class="save-icon">share</mat-icon></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -125,6 +125,10 @@ export class PlayerComponent implements OnInit {
|
|||||||
const already_has_filenames = !!this.fileNames;
|
const already_has_filenames = !!this.fileNames;
|
||||||
this.postsService.getFile(this.uid, null).subscribe(res => {
|
this.postsService.getFile(this.uid, null).subscribe(res => {
|
||||||
this.db_file = res['file'];
|
this.db_file = res['file'];
|
||||||
|
if (!this.db_file) {
|
||||||
|
this.openSnackBar('Failed to get file information from the server.', 'Dismiss');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!this.fileNames) {
|
if (!this.fileNames) {
|
||||||
// means it's a shared video
|
// means it's a shared video
|
||||||
if (!this.id) {
|
if (!this.id) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { THEMES_CONFIG } from '../themes';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { DOCUMENT } from '@angular/common';
|
import { DOCUMENT } from '@angular/common';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PostsService {
|
export class PostsService {
|
||||||
@@ -21,7 +22,9 @@ export class PostsService {
|
|||||||
theme;
|
theme;
|
||||||
settings_changed = new BehaviorSubject<boolean>(false);
|
settings_changed = new BehaviorSubject<boolean>(false);
|
||||||
auth_token = '4241b401-7236-493e-92b5-b72696b9d853';
|
auth_token = '4241b401-7236-493e-92b5-b72696b9d853';
|
||||||
|
session_id = null;
|
||||||
httpOptions = null;
|
httpOptions = null;
|
||||||
|
http_params: string = null;
|
||||||
|
|
||||||
debugMode = false;
|
debugMode = false;
|
||||||
constructor(private http: HttpClient, private router: Router, @Inject(DOCUMENT) private document: Document) {
|
constructor(private http: HttpClient, private router: Router, @Inject(DOCUMENT) private document: Document) {
|
||||||
@@ -29,15 +32,17 @@ export class PostsService {
|
|||||||
// this.startPath = window.location.href + '/api/';
|
// this.startPath = window.location.href + '/api/';
|
||||||
// this.startPathSSL = window.location.href + '/api/';
|
// this.startPathSSL = window.location.href + '/api/';
|
||||||
this.path = this.document.location.origin + '/api/';
|
this.path = this.document.location.origin + '/api/';
|
||||||
|
this.session_id = uuid();
|
||||||
if (isDevMode()) {
|
if (isDevMode()) {
|
||||||
this.debugMode = true;
|
this.debugMode = true;
|
||||||
this.path = 'http://localhost:17442/api/';
|
this.path = 'http://localhost:17442/api/';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.http_params = `apiKey=${this.auth_token}&sessionID=${this.session_id}`
|
||||||
|
|
||||||
this.httpOptions = {
|
this.httpOptions = {
|
||||||
params: new HttpParams({
|
params: new HttpParams({
|
||||||
fromString: `apiKey=${this.auth_token}`
|
fromString: this.http_params
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -63,25 +68,27 @@ export class PostsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable-next-line: max-line-length
|
// tslint:disable-next-line: max-line-length
|
||||||
makeMP3(url: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, customOutput: string = null, youtubeUsername: string = null, youtubePassword: string = null) {
|
makeMP3(url: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, customOutput: string = null, youtubeUsername: string = null, youtubePassword: string = null, ui_uid = null) {
|
||||||
return this.http.post(this.path + 'tomp3', {url: url,
|
return this.http.post(this.path + 'tomp3', {url: url,
|
||||||
maxBitrate: selectedQuality,
|
maxBitrate: selectedQuality,
|
||||||
customQualityConfiguration: customQualityConfiguration,
|
customQualityConfiguration: customQualityConfiguration,
|
||||||
customArgs: customArgs,
|
customArgs: customArgs,
|
||||||
customOutput: customOutput,
|
customOutput: customOutput,
|
||||||
youtubeUsername: youtubeUsername,
|
youtubeUsername: youtubeUsername,
|
||||||
youtubePassword: youtubePassword}, this.httpOptions);
|
youtubePassword: youtubePassword,
|
||||||
|
ui_uid: ui_uid}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable-next-line: max-line-length
|
// tslint:disable-next-line: max-line-length
|
||||||
makeMP4(url: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, customOutput: string = null, youtubeUsername: string = null, youtubePassword: string = null) {
|
makeMP4(url: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, customOutput: string = null, youtubeUsername: string = null, youtubePassword: string = null, ui_uid = null) {
|
||||||
return this.http.post(this.path + 'tomp4', {url: url,
|
return this.http.post(this.path + 'tomp4', {url: url,
|
||||||
selectedHeight: selectedQuality,
|
selectedHeight: selectedQuality,
|
||||||
customQualityConfiguration: customQualityConfiguration,
|
customQualityConfiguration: customQualityConfiguration,
|
||||||
customArgs: customArgs,
|
customArgs: customArgs,
|
||||||
customOutput: customOutput,
|
customOutput: customOutput,
|
||||||
youtubeUsername: youtubeUsername,
|
youtubeUsername: youtubeUsername,
|
||||||
youtubePassword: youtubePassword}, this.httpOptions);
|
youtubePassword: youtubePassword,
|
||||||
|
ui_uid: ui_uid}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
getFileStatusMp3(name: string) {
|
getFileStatusMp3(name: string) {
|
||||||
@@ -215,6 +222,23 @@ export class PostsService {
|
|||||||
return this.http.post(this.path + 'getAllSubscriptions', {}, this.httpOptions);
|
return this.http.post(this.path + 'getAllSubscriptions', {}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// current downloads
|
||||||
|
getCurrentDownloads() {
|
||||||
|
return this.http.get(this.path + 'downloads', this.httpOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// current download
|
||||||
|
getCurrentDownload(session_id, download_id) {
|
||||||
|
return this.http.post(this.path + 'download', {download_id: download_id, session_id: session_id}, this.httpOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear downloads. download_id is optional, if it exists only 1 download will be cleared
|
||||||
|
clearDownloads(delete_all = false, session_id = null, download_id = null) {
|
||||||
|
return this.http.post(this.path + 'clearDownloads', {delete_all: delete_all,
|
||||||
|
download_id: download_id,
|
||||||
|
session_id: session_id ? session_id : this.session_id}, this.httpOptions);
|
||||||
|
}
|
||||||
|
|
||||||
// updates the server to the latest version
|
// updates the server to the latest version
|
||||||
updateServer(tag) {
|
updateServer(tag) {
|
||||||
return this.http.post(this.path + 'updateServer', {tag: tag}, this.httpOptions);
|
return this.http.post(this.path + 'updateServer', {tag: tag}, this.httpOptions);
|
||||||
|
|||||||
Reference in New Issue
Block a user