diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000..3879838
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,71 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+name: "CodeQL"
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [master]
+ schedule:
+ - cron: '0 12 * * 6'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ # Override automatic language detection by changing the below list
+ # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
+ language: ['javascript']
+ # Learn more...
+ # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+ with:
+ # We must fetch at least the immediate parents so that if this is
+ # a pull request then we can checkout the head.
+ fetch-depth: 2
+
+ # If this run was triggered by a pull request event, then checkout
+ # the head of the pull request instead of the merge commit.
+ - run: git checkout HEAD^2
+ if: ${{ github.event_name == 'pull_request' }}
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v1
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 https://git.io/JvXDl
+
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
diff --git a/README.md b/README.md
index 65486db..1c22952 100644
--- a/README.md
+++ b/README.md
@@ -90,7 +90,7 @@ environment:
## API
-[API Docs](https://stoplight.io/p/docs/gh/tzahi12345/youtubedl-material?group=master&utm_campaign=publish_dialog&utm_source=studio)
+[API Docs](https://youtubedl-material.stoplight.io/docs/youtubedl-material/Public%20API%20v1.yaml)
To get started, go to the settings menu and enable the public API from the *Extra* tab. You can generate an API key if one is missing.
@@ -111,6 +111,7 @@ If you're interested in translating the app into a new language, check out the [
Official translators:
* Spanish - tzahi12345
* German - UnlimitedCookies
+* Chinese - TyRoyal
See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project.
diff --git a/backend/app.js b/backend/app.js
index 50af52e..19331f0 100644
--- a/backend/app.js
+++ b/backend/app.js
@@ -1,6 +1,6 @@
-var async = require('async');
const { uuid } = require('uuidv4');
var fs = require('fs-extra');
+var { promisify } = require('util');
var auth_api = require('./authentication/auth');
var winston = require('winston');
var path = require('path');
@@ -18,7 +18,6 @@ var utils = require('./utils')
var mergeFiles = require('merge-files');
const low = require('lowdb')
var ProgressBar = require('progress');
-var md5 = require('md5');
const NodeID3 = require('node-id3')
const downloader = require('youtube-dl/lib/downloader')
const fetch = require('node-fetch');
@@ -194,55 +193,60 @@ app.use(auth_api.passport.initialize());
// actual functions
-async function checkMigrations() {
- return new Promise(async resolve => {
- // 3.5->3.6 migration
- const files_to_db_migration_complete = true; // migration phased out! previous code: db.get('files_to_db_migration_complete').value();
-
- if (!files_to_db_migration_complete) {
- logger.info('Beginning migration: 3.5->3.6+')
- runFilesToDBMigration().then(success => {
- if (success) { logger.info('3.5->3.6+ migration complete!'); }
- else { logger.error('Migration failed: 3.5->3.6+'); }
- });
- }
-
- resolve(true);
+/**
+ * setTimeout, but its a promise.
+ * @param {number} ms
+ */
+async function wait(ms) {
+ await new Promise(resolve => {
+ setTimeout(resolve, ms);
});
}
+async function checkMigrations() {
+ // 3.5->3.6 migration
+ const files_to_db_migration_complete = true; // migration phased out! previous code: db.get('files_to_db_migration_complete').value();
+
+ if (!files_to_db_migration_complete) {
+ logger.info('Beginning migration: 3.5->3.6+')
+ const success = await runFilesToDBMigration()
+ if (success) { logger.info('3.5->3.6+ migration complete!'); }
+ else { logger.error('Migration failed: 3.5->3.6+'); }
+ }
+
+ return true;
+}
+
async function runFilesToDBMigration() {
- return new Promise(async resolve => {
- try {
- let mp3s = getMp3s();
- let mp4s = getMp4s();
+ try {
+ let mp3s = await getMp3s();
+ let mp4s = await getMp4s();
- for (let i = 0; i < mp3s.length; i++) {
- let file_obj = mp3s[i];
- const file_already_in_db = db.get('files.audio').find({id: file_obj.id}).value();
- if (!file_already_in_db) {
- logger.verbose(`Migrating file ${file_obj.id}`);
- db_api.registerFileDB(file_obj.id + '.mp3', 'audio');
- }
+ for (let i = 0; i < mp3s.length; i++) {
+ let file_obj = mp3s[i];
+ const file_already_in_db = db.get('files.audio').find({id: file_obj.id}).value();
+ if (!file_already_in_db) {
+ logger.verbose(`Migrating file ${file_obj.id}`);
+ await db_api.registerFileDB(file_obj.id + '.mp3', 'audio');
}
-
- for (let i = 0; i < mp4s.length; i++) {
- let file_obj = mp4s[i];
- const file_already_in_db = db.get('files.video').find({id: file_obj.id}).value();
- if (!file_already_in_db) {
- logger.verbose(`Migrating file ${file_obj.id}`);
- db_api.registerFileDB(file_obj.id + '.mp4', 'video');
- }
- }
-
- // sets migration to complete
- db.set('files_to_db_migration_complete', true).write();
- resolve(true);
- } catch(err) {
- logger.error(err);
- resolve(false);
}
- });
+
+ for (let i = 0; i < mp4s.length; i++) {
+ let file_obj = mp4s[i];
+ const file_already_in_db = db.get('files.video').find({id: file_obj.id}).value();
+ if (!file_already_in_db) {
+ logger.verbose(`Migrating file ${file_obj.id}`);
+ await db_api.registerFileDB(file_obj.id + '.mp4', 'video');
+ }
+ }
+
+ // sets migration to complete
+ db.set('files_to_db_migration_complete', true).write();
+ return true;
+ } catch(err) {
+ logger.error(err);
+ return false;
+ }
}
async function startServer() {
@@ -417,20 +421,20 @@ async function downloadReleaseZip(tag) {
}
async function installDependencies() {
- return new Promise(resolve => {
- var child_process = require('child_process');
- child_process.execSync('npm install',{stdio:[0,1,2]});
- resolve(true);
- });
+ var child_process = require('child_process');
+ var exec = promisify(child_process.exec);
+ await exec('npm install',{stdio:[0,1,2]});
+ return true;
}
async function backupServerLite() {
- return new Promise(async resolve => {
- fs.ensureDirSync(path.join(__dirname, 'appdata', 'backups'));
- let output_path = path.join('appdata', 'backups', `backup-${Date.now()}.zip`);
- logger.info(`Backing up your non-video/audio files to ${output_path}. This may take up to a few seconds/minutes.`);
- let output = fs.createWriteStream(path.join(__dirname, output_path));
+ await fs.ensureDir(path.join(__dirname, 'appdata', 'backups'));
+ let output_path = path.join('appdata', 'backups', `backup-${Date.now()}.zip`);
+ logger.info(`Backing up your non-video/audio files to ${output_path}. This may take up to a few seconds/minutes.`);
+ let output = fs.createWriteStream(path.join(__dirname, output_path));
+
+ await new Promise(resolve => {
var archive = archiver('zip', {
gzip: true,
zlib: { level: 9 } // Sets the compression level.
@@ -454,87 +458,80 @@ async function backupServerLite() {
ignore: files_to_ignore
});
- await archive.finalize();
-
- // wait a tiny bit for the zip to reload in fs
- setTimeout(function() {
- resolve(true);
- }, 100);
+ resolve(archive.finalize());
});
+
+ // wait a tiny bit for the zip to reload in fs
+ await wait(100);
+ return true;
}
async function isNewVersionAvailable() {
- return new Promise(async resolve => {
- // gets tag of the latest version of youtubedl-material, compare to current version
- const latest_tag = await getLatestVersion();
- const current_tag = CONSTS['CURRENT_VERSION'];
- if (latest_tag > current_tag) {
- resolve(true);
- } else {
- resolve(false);
- }
- });
+ // gets tag of the latest version of youtubedl-material, compare to current version
+ const latest_tag = await getLatestVersion();
+ const current_tag = CONSTS['CURRENT_VERSION'];
+ if (latest_tag > current_tag) {
+ return true;
+ } else {
+ return false;
+ }
}
async function getLatestVersion() {
- return new Promise(resolve => {
- fetch('https://api.github.com/repos/tzahi12345/youtubedl-material/releases/latest', {method: 'Get'})
- .then(async res => res.json())
- .then(async (json) => {
- if (json['message']) {
- // means there's an error in getting latest version
- logger.error(`ERROR: Received the following message from GitHub's API:`);
- logger.error(json['message']);
- if (json['documentation_url']) logger.error(`Associated URL: ${json['documentation_url']}`)
- }
- resolve(json['tag_name']);
- return;
- });
- });
+ const res = await fetch('https://api.github.com/repos/tzahi12345/youtubedl-material/releases/latest', {method: 'Get'});
+ const json = await res.json();
+
+ if (json['message']) {
+ // means there's an error in getting latest version
+ logger.error(`ERROR: Received the following message from GitHub's API:`);
+ logger.error(json['message']);
+ if (json['documentation_url']) logger.error(`Associated URL: ${json['documentation_url']}`)
+ }
+ return json['tag_name'];
}
async function killAllDownloads() {
- return new Promise(resolve => {
- ps.lookup({
- command: 'youtube-dl',
- }, function(err, resultList ) {
- if (err) {
- // failed to get list of processes
- logger.error('Failed to get a list of running youtube-dl processes.');
- logger.error(err);
- resolve({
- details: err,
- success: false
- });
- }
-
- // processes that contain the string 'youtube-dl' in the name will be looped
- resultList.forEach(function( process ){
- if (process) {
- ps.kill(process.pid, 'SIGKILL', function( err ) {
- if (err) {
- // failed to kill, process may have ended on its own
- logger.warn(`Failed to kill process with PID ${process.pid}`);
- logger.warn(err);
- }
- else {
- logger.verbose(`Process ${process.pid} has been killed!`);
- }
- });
+ const lookupAsync = promisify(ps.lookup);
+
+ try {
+ await lookupAsync({
+ command: 'youtube-dl'
+ });
+ } catch (err) {
+ // failed to get list of processes
+ logger.error('Failed to get a list of running youtube-dl processes.');
+ logger.error(err);
+ return {
+ details: err,
+ success: false
+ };
+ }
+
+ // processes that contain the string 'youtube-dl' in the name will be looped
+ resultList.forEach(function( process ){
+ if (process) {
+ ps.kill(process.pid, 'SIGKILL', function( err ) {
+ if (err) {
+ // failed to kill, process may have ended on its own
+ logger.warn(`Failed to kill process with PID ${process.pid}`);
+ logger.warn(err);
+ }
+ else {
+ logger.verbose(`Process ${process.pid} has been killed!`);
}
});
- resolve({
- success: true
- });
- });
+ }
});
+
+ return {
+ success: true
+ };
}
async function setPortItemFromENV() {
- return new Promise(resolve => {
- config_api.setConfigItem('ytdl_port', backendPort.toString());
- setTimeout(() => resolve(true), 100);
- });
+ config_api.setConfigItem('ytdl_port', backendPort.toString());
+ await wait(100);
+ return true;
}
async function setAndLoadConfig() {
@@ -543,51 +540,45 @@ async function setAndLoadConfig() {
}
async function setConfigFromEnv() {
- return new Promise(resolve => {
- let config_items = getEnvConfigItems();
- let success = config_api.setConfigItems(config_items);
- if (success) {
- logger.info('Config items set using ENV variables.');
- setTimeout(() => resolve(true), 100);
- } else {
- logger.error('ERROR: Failed to set config items using ENV variables.');
- resolve(false);
- }
- });
+ let config_items = getEnvConfigItems();
+ let success = config_api.setConfigItems(config_items);
+ if (success) {
+ logger.info('Config items set using ENV variables.');
+ await wait(100);
+ return true;
+ } else {
+ logger.error('ERROR: Failed to set config items using ENV variables.');
+ return false;
+ }
}
async function loadConfig() {
- return new Promise(async resolve => {
- loadConfigValues();
+ loadConfigValues();
- // creates archive path if missing
- if (!fs.existsSync(archivePath)){
- fs.mkdirSync(archivePath);
- }
+ // creates archive path if missing
+ await fs.ensureDir(archivePath);
- // get subscriptions
- if (allowSubscriptions) {
- // runs initially, then runs every ${subscriptionCheckInterval} seconds
+ // get subscriptions
+ if (allowSubscriptions) {
+ // runs initially, then runs every ${subscriptionCheckInterval} seconds
+ watchSubscriptions();
+ setInterval(() => {
watchSubscriptions();
- setInterval(() => {
- watchSubscriptions();
- }, subscriptionsCheckInterval * 1000);
- }
+ }, subscriptionsCheckInterval * 1000);
+ }
- db_api.importUnregisteredFiles();
+ db_api.importUnregisteredFiles();
- // check migrations
- await checkMigrations();
+ // check migrations
+ await checkMigrations();
- // load in previous downloads
- downloads = db.get('downloads').value();
+ // load in previous downloads
+ downloads = db.get('downloads').value();
- // start the server here
- startServer();
-
- resolve(true);
- });
+ // start the server here
+ startServer();
+ return true;
}
function loadConfigValues() {
@@ -704,17 +695,17 @@ function generateEnvVarConfigItem(key) {
return {key: key, value: process['env'][key]};
}
-function getMp3s() {
+async function getMp3s() {
let mp3s = [];
- var files = utils.recFindByExt(audioFolderPath, 'mp3'); // fs.readdirSync(audioFolderPath);
+ var files = await utils.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 stats = fs.statSync(file);
+ var stats = await fs.stat(file);
var id = file_path.substring(0, file_path.length-4);
- var jsonobj = utils.getJSONMp3(id, audioFolderPath);
+ var jsonobj = await utils.getJSONMp3(id, audioFolderPath);
if (!jsonobj) continue;
var title = jsonobj.title;
var url = jsonobj.webpage_url;
@@ -733,9 +724,9 @@ function getMp3s() {
return mp3s;
}
-function getMp4s(relative_path = true) {
+async function getMp4s(relative_path = true) {
let mp4s = [];
- var files = utils.recFindByExt(videoFolderPath, 'mp4');
+ var files = await utils.recFindByExt(videoFolderPath, 'mp4');
for (let i = 0; i < files.length; i++) {
let file = files[i];
var file_path = file.substring(videoFolderPath.length, file.length);
@@ -743,7 +734,7 @@ function getMp4s(relative_path = true) {
var stats = fs.statSync(file);
var id = file_path.substring(0, file_path.length-4);
- var jsonobj = utils.getJSONMp4(id, videoFolderPath);
+ var jsonobj = await utils.getJSONMp4(id, videoFolderPath);
if (!jsonobj) continue;
var title = jsonobj.title;
var url = jsonobj.webpage_url;
@@ -848,260 +839,231 @@ function getVideoFormatID(name)
}
}
-async function createPlaylistZipFile(fileNames, type, outputName, fullPathProvided = null) {
- return new Promise(async resolve => {
- let zipFolderPath = null;
+async function createPlaylistZipFile(fileNames, type, outputName, fullPathProvided = null, user_uid = null) {
+ let zipFolderPath = null;
- if (!fullPathProvided) {
- zipFolderPath = path.join(__dirname, (type === 'audio') ? audioFolderPath : videoFolderPath);
- } else {
- zipFolderPath = path.join(__dirname, config_api.getConfigItem('ytdl_subscriptions_base_path'));
- }
+ if (!fullPathProvided) {
+ zipFolderPath = path.join(__dirname, (type === 'audio') ? audioFolderPath : videoFolderPath);
+ if (user_uid) zipFolderPath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, zipFolderPath);
+ } else {
+ zipFolderPath = path.join(__dirname, config_api.getConfigItem('ytdl_subscriptions_base_path'));
+ }
- let ext = (type === 'audio') ? '.mp3' : '.mp4';
+ let ext = (type === 'audio') ? '.mp3' : '.mp4';
- let output = fs.createWriteStream(path.join(zipFolderPath, outputName + '.zip'));
-
- var archive = archiver('zip', {
- gzip: true,
- zlib: { level: 9 } // Sets the compression level.
- });
-
- archive.on('error', function(err) {
- logger.error(err);
- throw err;
- });
-
- // pipe archive data to the output file
- archive.pipe(output);
-
- for (let i = 0; i < fileNames.length; i++) {
- let fileName = fileNames[i];
- let fileNamePathRemoved = path.parse(fileName).base;
- let file_path = !fullPathProvided ? zipFolderPath + fileName + ext : fileName;
- archive.file(file_path, {name: fileNamePathRemoved + ext})
- }
-
- await archive.finalize();
-
- // wait a tiny bit for the zip to reload in fs
- setTimeout(function() {
- resolve(path.join(zipFolderPath,outputName + '.zip'));
- }, 100);
+ let output = fs.createWriteStream(path.join(zipFolderPath, outputName + '.zip'));
+ var archive = archiver('zip', {
+ gzip: true,
+ zlib: { level: 9 } // Sets the compression level.
});
+ archive.on('error', function(err) {
+ logger.error(err);
+ throw err;
+ });
+ // pipe archive data to the output file
+ archive.pipe(output);
+
+ for (let i = 0; i < fileNames.length; i++) {
+ let fileName = fileNames[i];
+ let fileNamePathRemoved = path.parse(fileName).base;
+ let file_path = !fullPathProvided ? path.join(zipFolderPath, fileName + ext) : fileName;
+ archive.file(file_path, {name: fileNamePathRemoved + ext})
+ }
+
+ await archive.finalize();
+
+ // wait a tiny bit for the zip to reload in fs
+ await wait(100);
+ return path.join(zipFolderPath,outputName + '.zip');
}
async function deleteAudioFile(name, customPath = null, blacklistMode = false) {
- return new Promise(resolve => {
- let filePath = customPath ? customPath : audioFolderPath;
-
- var jsonPath = path.join(filePath,name+'.mp3.info.json');
- var altJSONPath = path.join(filePath,name+'.info.json');
- var audioFilePath = path.join(filePath,name+'.mp3');
- var thumbnailPath = path.join(filePath,name+'.webp');
- var altThumbnailPath = path.join(filePath,name+'.jpg');
+ let filePath = customPath ? customPath : audioFolderPath;
- jsonPath = path.join(__dirname, jsonPath);
- altJSONPath = path.join(__dirname, altJSONPath);
- audioFilePath = path.join(__dirname, audioFilePath);
+ var jsonPath = path.join(filePath,name+'.mp3.info.json');
+ var altJSONPath = path.join(filePath,name+'.info.json');
+ var audioFilePath = path.join(filePath,name+'.mp3');
+ var thumbnailPath = path.join(filePath,name+'.webp');
+ var altThumbnailPath = path.join(filePath,name+'.jpg');
- let jsonExists = fs.existsSync(jsonPath);
- let thumbnailExists = fs.existsSync(thumbnailPath);
+ jsonPath = path.join(__dirname, jsonPath);
+ altJSONPath = path.join(__dirname, altJSONPath);
+ audioFilePath = path.join(__dirname, audioFilePath);
- if (!jsonExists) {
- if (fs.existsSync(altJSONPath)) {
- jsonExists = true;
- jsonPath = altJSONPath;
- }
+ let jsonExists = await fs.pathExists(jsonPath);
+ let thumbnailExists = await fs.pathExists(thumbnailPath);
+
+ if (!jsonExists) {
+ if (await fs.pathExists(altJSONPath)) {
+ jsonExists = true;
+ jsonPath = altJSONPath;
}
+ }
- if (!thumbnailExists) {
- if (fs.existsSync(altThumbnailPath)) {
- thumbnailExists = true;
- thumbnailPath = altThumbnailPath;
- }
+ if (!thumbnailExists) {
+ if (await fs.pathExists(altThumbnailPath)) {
+ thumbnailExists = true;
+ thumbnailPath = altThumbnailPath;
}
+ }
- let audioFileExists = fs.existsSync(audioFilePath);
-
- if (config_api.descriptors[name]) {
- try {
- for (let i = 0; i < config_api.descriptors[name].length; i++) {
- config_api.descriptors[name][i].destroy();
- }
- } catch(e) {
+ let audioFileExists = await fs.pathExists(audioFilePath);
+ if (config_api.descriptors[name]) {
+ try {
+ for (let i = 0; i < config_api.descriptors[name].length; i++) {
+ config_api.descriptors[name][i].destroy();
}
+ } catch(e) {
+
}
+ }
- let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
- if (useYoutubeDLArchive) {
- const archive_path = path.join(archivePath, 'archive_audio.txt');
+ let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
+ if (useYoutubeDLArchive) {
+ const archive_path = path.join(archivePath, 'archive_audio.txt');
- // get ID from JSON
+ // get ID from JSON
- var jsonobj = utils.getJSONMp3(name, filePath);
- let id = null;
- if (jsonobj) id = jsonobj.id;
+ var jsonobj = await utils.getJSONMp3(name, filePath);
+ let id = null;
+ if (jsonobj) id = jsonobj.id;
- // use subscriptions API to remove video from the archive file, and write it to the blacklist
- if (fs.existsSync(archive_path)) {
- const line = id ? subscriptions_api.removeIDFromArchive(archive_path, id) : null;
- if (blacklistMode && line) writeToBlacklist('audio', line);
- } else {
- logger.info('Could not find archive file for audio files. Creating...');
- fs.closeSync(fs.openSync(archive_path, 'w'));
- }
- }
-
- if (jsonExists) fs.unlinkSync(jsonPath);
- if (thumbnailExists) fs.unlinkSync(thumbnailPath);
- if (audioFileExists) {
- fs.unlink(audioFilePath, function(err) {
- if (fs.existsSync(jsonPath) || fs.existsSync(audioFilePath)) {
- resolve(false);
- } else {
- resolve(true);
- }
- });
+ // use subscriptions API to remove video from the archive file, and write it to the blacklist
+ if (await fs.pathExists(archive_path)) {
+ const line = id ? await subscriptions_api.removeIDFromArchive(archive_path, id) : null;
+ if (blacklistMode && line) await writeToBlacklist('audio', line);
} else {
- // TODO: tell user that the file didn't exist
- resolve(true);
+ logger.info('Could not find archive file for audio files. Creating...');
+ await fs.close(await fs.open(archive_path, 'w'));
}
+ }
- });
+ if (jsonExists) await fs.unlink(jsonPath);
+ if (thumbnailExists) await fs.unlink(thumbnailPath);
+ if (audioFileExists) {
+ await fs.unlink(audioFilePath);
+ if (await fs.pathExists(jsonPath) || await fs.pathExists(audioFilePath)) {
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ // TODO: tell user that the file didn't exist
+ return true;
+ }
}
async function deleteVideoFile(name, customPath = null, blacklistMode = false) {
- return new Promise(resolve => {
- let filePath = customPath ? customPath : videoFolderPath;
- var jsonPath = path.join(filePath,name+'.info.json');
+ let filePath = customPath ? customPath : videoFolderPath;
+ var jsonPath = path.join(filePath,name+'.info.json');
- var altJSONPath = path.join(filePath,name+'.mp4.info.json');
- var videoFilePath = path.join(filePath,name+'.mp4');
- var thumbnailPath = path.join(filePath,name+'.webp');
- var altThumbnailPath = path.join(filePath,name+'.jpg');
+ var altJSONPath = path.join(filePath,name+'.mp4.info.json');
+ var videoFilePath = path.join(filePath,name+'.mp4');
+ var thumbnailPath = path.join(filePath,name+'.webp');
+ var altThumbnailPath = path.join(filePath,name+'.jpg');
- jsonPath = path.join(__dirname, jsonPath);
- videoFilePath = path.join(__dirname, videoFilePath);
+ jsonPath = path.join(__dirname, jsonPath);
+ videoFilePath = path.join(__dirname, videoFilePath);
- let jsonExists = fs.existsSync(jsonPath);
- let videoFileExists = fs.existsSync(videoFilePath);
- let thumbnailExists = fs.existsSync(thumbnailPath);
+ let jsonExists = await fs.pathExists(jsonPath);
+ let videoFileExists = await fs.pathExists(videoFilePath);
+ let thumbnailExists = await fs.pathExists(thumbnailPath);
- if (!jsonExists) {
- if (fs.existsSync(altJSONPath)) {
- jsonExists = true;
- jsonPath = altJSONPath;
- }
+ if (!jsonExists) {
+ if (await fs.pathExists(altJSONPath)) {
+ jsonExists = true;
+ jsonPath = altJSONPath;
}
-
- if (!thumbnailExists) {
- if (fs.existsSync(altThumbnailPath)) {
- thumbnailExists = true;
- thumbnailPath = altThumbnailPath;
- }
+ }
+
+ if (!thumbnailExists) {
+ if (await fs.pathExists(altThumbnailPath)) {
+ thumbnailExists = true;
+ thumbnailPath = altThumbnailPath;
}
+ }
- if (config_api.descriptors[name]) {
- try {
- for (let i = 0; i < config_api.descriptors[name].length; i++) {
- config_api.descriptors[name][i].destroy();
- }
- } catch(e) {
-
+ if (config_api.descriptors[name]) {
+ try {
+ for (let i = 0; i < config_api.descriptors[name].length; i++) {
+ config_api.descriptors[name][i].destroy();
}
+ } catch(e) {
+
}
+ }
- let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
- if (useYoutubeDLArchive) {
- const archive_path = path.join(archivePath, 'archive_video.txt');
+ let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
+ if (useYoutubeDLArchive) {
+ const archive_path = path.join(archivePath, 'archive_video.txt');
- // get ID from JSON
+ // get ID from JSON
- var jsonobj = utils.getJSONMp4(name, filePath);
- let id = null;
- if (jsonobj) id = jsonobj.id;
+ var jsonobj = await utils.getJSONMp4(name, filePath);
+ let id = null;
+ if (jsonobj) id = jsonobj.id;
- // use subscriptions API to remove video from the archive file, and write it to the blacklist
- if (fs.existsSync(archive_path)) {
- const line = id ? subscriptions_api.removeIDFromArchive(archive_path, id) : null;
- if (blacklistMode && line) writeToBlacklist('video', line);
- } else {
- logger.info('Could not find archive file for videos. Creating...');
- fs.closeSync(fs.openSync(archive_path, 'w'));
- }
- }
-
- if (jsonExists) fs.unlinkSync(jsonPath);
- if (thumbnailExists) fs.unlinkSync(thumbnailPath);
- if (videoFileExists) {
- fs.unlink(videoFilePath, function(err) {
- if (fs.existsSync(jsonPath) || fs.existsSync(videoFilePath)) {
- resolve(false);
- } else {
- resolve(true);
- }
- });
+ // use subscriptions API to remove video from the archive file, and write it to the blacklist
+ if (await fs.pathExists(archive_path)) {
+ const line = id ? await subscriptions_api.removeIDFromArchive(archive_path, id) : null;
+ if (blacklistMode && line) await writeToBlacklist('video', line);
} else {
- // TODO: tell user that the file didn't exist
- resolve(true);
+ logger.info('Could not find archive file for videos. Creating...');
+ fs.closeSync(fs.openSync(archive_path, 'w'));
+ }
+ }
+
+ if (jsonExists) await fs.unlink(jsonPath);
+ if (thumbnailExists) await fs.unlink(thumbnailPath);
+ if (videoFileExists) {
+ await fs.unlink(videoFilePath);
+ if (await fs.pathExists(jsonPath) || await fs.pathExists(videoFilePath)) {
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ // TODO: tell user that the file didn't exist
+ return true;
+ }
+}
+
+/**
+ * @param {'audio' | 'video'} type
+ * @param {string[]} fileNames
+ */
+async function getAudioOrVideoInfos(type, fileNames) {
+ let result = await Promise.all(fileNames.map(async fileName => {
+ let fileLocation = videoFolderPath+fileName;
+ if (type === 'audio') {
+ fileLocation += '.mp3.info.json';
+ } else if (type === 'video') {
+ fileLocation += '.info.json';
}
- });
-}
-
-// replaces .webm with appropriate extension
-function getTrueFileName(unfixed_path, type) {
- let fixed_path = unfixed_path;
-
- const new_ext = (type === 'audio' ? 'mp3' : 'mp4');
- let unfixed_parts = unfixed_path.split('.');
- const old_ext = unfixed_parts[unfixed_parts.length-1];
-
-
- if (old_ext !== new_ext) {
- unfixed_parts[unfixed_parts.length-1] = new_ext;
- fixed_path = unfixed_parts.join('.');
- }
- return fixed_path;
-}
-
-function getAudioInfos(fileNames) {
- let result = [];
- for (let i = 0; i < fileNames.length; i++) {
- let fileName = fileNames[i];
- let fileLocation = audioFolderPath+fileName+'.mp3.info.json';
- if (fs.existsSync(fileLocation)) {
- let data = fs.readFileSync(fileLocation);
+ if (await fs.pathExists(fileLocation)) {
+ let data = await fs.readFile(fileLocation);
try {
- result.push(JSON.parse(data));
- } catch(e) {
- logger.error(`Could not find info for file ${fileName}.mp3`);
+ return JSON.parse(data);
+ } catch (e) {
+ let suffix;
+ if (type === 'audio') {
+ suffix += '.mp3';
+ } else if (type === 'video') {
+ suffix += '.mp4';
+ }
+
+ logger.error(`Could not find info for file ${fileName}${suffix}`);
}
}
- }
- return result;
-}
+ return null;
+ }));
-function getVideoInfos(fileNames) {
- let result = [];
- for (let i = 0; i < fileNames.length; i++) {
- let fileName = fileNames[i];
- let fileLocation = videoFolderPath+fileName+'.info.json';
- if (fs.existsSync(fileLocation)) {
- let data = fs.readFileSync(fileLocation);
- try {
- result.push(JSON.parse(data));
- } catch(e) {
- logger.error(`Could not find info for file ${fileName}.mp4`);
- }
- }
- }
- return result;
+ return result.filter(data => data != null);
}
// downloads
@@ -1414,134 +1376,131 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) {
}
async function generateArgs(url, type, options) {
- return new Promise(async resolve => {
- var videopath = '%(title)s';
- var globalArgs = config_api.getConfigItem('ytdl_custom_args');
- let useCookies = config_api.getConfigItem('ytdl_use_cookies');
- var is_audio = type === 'audio';
+ var videopath = '%(title)s';
+ var globalArgs = config_api.getConfigItem('ytdl_custom_args');
+ let useCookies = config_api.getConfigItem('ytdl_use_cookies');
+ var is_audio = type === 'audio';
- var fileFolderPath = is_audio ? audioFolderPath : videoFolderPath;
+ var fileFolderPath = is_audio ? audioFolderPath : videoFolderPath;
- if (options.customFileFolderPath) fileFolderPath = options.customFileFolderPath;
+ if (options.customFileFolderPath) fileFolderPath = options.customFileFolderPath;
- var customArgs = options.customArgs;
- var customOutput = options.customOutput;
- var customQualityConfiguration = options.customQualityConfiguration;
+ var customArgs = options.customArgs;
+ var customOutput = options.customOutput;
+ var customQualityConfiguration = options.customQualityConfiguration;
- // video-specific args
- var selectedHeight = options.selectedHeight;
+ // video-specific args
+ var selectedHeight = options.selectedHeight;
- // audio-specific args
- var maxBitrate = options.maxBitrate;
+ // audio-specific args
+ var maxBitrate = options.maxBitrate;
- var youtubeUsername = options.youtubeUsername;
- var youtubePassword = options.youtubePassword;
+ var youtubeUsername = options.youtubeUsername;
+ var youtubePassword = options.youtubePassword;
- let downloadConfig = null;
- let qualityPath = (is_audio && !options.skip_audio_args) ? ['-f', 'bestaudio'] : ['-f', 'bestvideo+bestaudio', '--merge-output-format', 'mp4'];
- const is_youtube = url.includes('youtu');
- if (!is_audio && !is_youtube) {
- // tiktok videos fail when using the default format
- qualityPath = null;
- } else if (!is_audio && !is_youtube && (url.includes('reddit') || url.includes('pornhub'))) {
- qualityPath = ['-f', 'bestvideo+bestaudio']
+ let downloadConfig = null;
+ let qualityPath = (is_audio && !options.skip_audio_args) ? ['-f', 'bestaudio'] : ['-f', 'bestvideo+bestaudio', '--merge-output-format', 'mp4'];
+ const is_youtube = url.includes('youtu');
+ if (!is_audio && !is_youtube) {
+ // tiktok videos fail when using the default format
+ qualityPath = null;
+ } else if (!is_audio && !is_youtube && (url.includes('reddit') || url.includes('pornhub'))) {
+ qualityPath = ['-f', 'bestvideo+bestaudio']
+ }
+
+ if (customArgs) {
+ downloadConfig = customArgs.split(',,');
+ } else {
+ if (customQualityConfiguration) {
+ qualityPath = ['-f', customQualityConfiguration];
+ } else if (selectedHeight && selectedHeight !== '' && !is_audio) {
+ qualityPath = ['-f', `'(mp4)[height=${selectedHeight}'`];
+ } else if (maxBitrate && is_audio) {
+ qualityPath = ['--audio-quality', maxBitrate]
}
- if (customArgs) {
- downloadConfig = customArgs.split(',,');
+ if (customOutput) {
+ downloadConfig = ['-o', path.join(fileFolderPath, customOutput) + ".%(ext)s", '--write-info-json', '--print-json'];
} else {
- if (customQualityConfiguration) {
- qualityPath = ['-f', customQualityConfiguration];
- } else if (selectedHeight && selectedHeight !== '' && !is_audio) {
- qualityPath = ['-f', `'(mp4)[height=${selectedHeight}'`];
- } else if (maxBitrate && is_audio) {
- qualityPath = ['--audio-quality', maxBitrate]
- }
-
- if (customOutput) {
- customOutput = options.noRelativePath ? customOutput : path.join(fileFolderPath, customOutput);
- downloadConfig = ['-o', `${customOutput}.%(ext)s`, '--write-info-json', '--print-json'];
- } else {
- downloadConfig = ['-o', path.join(fileFolderPath, videopath + (is_audio ? '.%(ext)s' : '.mp4')), '--write-info-json', '--print-json'];
- }
-
- if (qualityPath && options.downloading_method === 'exec') downloadConfig.push(...qualityPath);
-
- if (is_audio && !options.skip_audio_args) {
- downloadConfig.push('-x');
- downloadConfig.push('--audio-format', 'mp3');
- }
-
- if (youtubeUsername && youtubePassword) {
- downloadConfig.push('--username', youtubeUsername, '--password', youtubePassword);
- }
-
- if (useCookies) {
- if (fs.existsSync(path.join(__dirname, 'appdata', 'cookies.txt'))) {
- downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
- } else {
- logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
- }
- }
-
- if (!useDefaultDownloadingAgent && customDownloadingAgent) {
- downloadConfig.splice(0, 0, '--external-downloader', customDownloadingAgent);
- }
-
- let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
- if (useYoutubeDLArchive) {
- const archive_folder = options.user ? path.join(fileFolderPath, 'archives') : archivePath;
- const archive_path = path.join(archive_folder, `archive_${type}.txt`);
-
- fs.ensureDirSync(archive_folder);
-
- // create archive file if it doesn't exist
- if (!fs.existsSync(archive_path)) {
- fs.closeSync(fs.openSync(archive_path, 'w'));
- }
-
- let blacklist_path = options.user ? path.join(fileFolderPath, 'archives', `blacklist_${type}.txt`) : 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 = path.join(fileFolderPath, `merged_${type}.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 (config_api.getConfigItem('ytdl_include_thumbnail')) {
- downloadConfig.push('--write-thumbnail');
- }
-
- if (globalArgs && globalArgs !== '') {
- // adds global args
- if (downloadConfig.indexOf('-o') !== -1 && globalArgs.split(',,').indexOf('-o') !== -1) {
- // if global args has an output, replce the original output with that of global args
- const original_output_index = downloadConfig.indexOf('-o');
- downloadConfig.splice(original_output_index, 2);
- }
- downloadConfig = downloadConfig.concat(globalArgs.split(',,'));
- }
-
+ downloadConfig = ['-o', path.join(fileFolderPath, videopath + (is_audio ? '.%(ext)s' : '.mp4')), '--write-info-json', '--print-json'];
}
- logger.verbose(`youtube-dl args being used: ${downloadConfig.join(',')}`);
- resolve(downloadConfig);
- });
+
+ if (qualityPath && options.downloading_method === 'exec') downloadConfig.push(...qualityPath);
+
+ if (is_audio && !options.skip_audio_args) {
+ downloadConfig.push('-x');
+ downloadConfig.push('--audio-format', 'mp3');
+ }
+
+ if (youtubeUsername && youtubePassword) {
+ downloadConfig.push('--username', youtubeUsername, '--password', youtubePassword);
+ }
+
+ if (useCookies) {
+ if (await fs.pathExists(path.join(__dirname, 'appdata', 'cookies.txt'))) {
+ downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
+ } else {
+ logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
+ }
+ }
+
+ if (!useDefaultDownloadingAgent && customDownloadingAgent) {
+ downloadConfig.splice(0, 0, '--external-downloader', customDownloadingAgent);
+ }
+
+ let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
+ if (useYoutubeDLArchive) {
+ const archive_folder = options.user ? path.join(fileFolderPath, 'archives') : archivePath;
+ const archive_path = path.join(archive_folder, `archive_${type}.txt`);
+
+ await fs.ensureDir(archive_folder);
+
+ // create archive file if it doesn't exist
+ if (!(await fs.pathExists(archive_path))) {
+ await fs.close(await fs.open(archive_path, 'w'));
+ }
+
+ let blacklist_path = options.user ? path.join(fileFolderPath, 'archives', `blacklist_${type}.txt`) : path.join(archivePath, `blacklist_${type}.txt`);
+ // create blacklist file if it doesn't exist
+ if (!(await fs.pathExists(blacklist_path))) {
+ await fs.close(await fs.open(blacklist_path, 'w'));
+ }
+
+ let merged_path = path.join(fileFolderPath, `merged_${type}.txt`);
+ await fs.ensureFile(merged_path);
+ // merges blacklist and regular archive
+ let inputPathList = [archive_path, blacklist_path];
+ let status = await mergeFiles(inputPathList, merged_path);
+
+ options.merged_string = await fs.readFile(merged_path, "utf8");
+
+ downloadConfig.push('--download-archive', merged_path);
+ }
+
+ if (config_api.getConfigItem('ytdl_include_thumbnail')) {
+ downloadConfig.push('--write-thumbnail');
+ }
+
+ if (globalArgs && globalArgs !== '') {
+ // adds global args
+ if (downloadConfig.indexOf('-o') !== -1 && globalArgs.split(',,').indexOf('-o') !== -1) {
+ // if global args has an output, replce the original output with that of global args
+ const original_output_index = downloadConfig.indexOf('-o');
+ downloadConfig.splice(original_output_index, 2);
+ }
+ downloadConfig = downloadConfig.concat(globalArgs.split(',,'));
+ }
+
+ }
+ logger.verbose(`youtube-dl args being used: ${downloadConfig.join(',')}`);
+ return downloadConfig;
}
async function getVideoInfoByURL(url, args = [], download = null) {
return new Promise(resolve => {
// remove bad args
const new_args = [...args];
-
+
const archiveArgIndex = new_args.indexOf('--download-archive');
if (archiveArgIndex !== -1) {
new_args.splice(archiveArgIndex, 2);
@@ -1607,11 +1566,11 @@ async function convertFileToMp3(input_file, output_file) {
});
}
-function writeToBlacklist(type, line) {
+async 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
line = '\n' + line;
- fs.appendFileSync(blacklistPath, line);
+ await fs.appendFile(blacklistPath, line);
}
// download management functions
@@ -1755,21 +1714,6 @@ function removeFileExtension(filename) {
return filename_parts.join('.');
}
-// https://stackoverflow.com/a/32197381/8088021
-const deleteFolderRecursive = function(folder_to_delete) {
- if (fs.existsSync(folder_to_delete)) {
- fs.readdirSync(folder_to_delete).forEach((file, index) => {
- const curPath = path.join(folder_to_delete, file);
- if (fs.lstatSync(curPath).isDirectory()) { // recurse
- deleteFolderRecursive(curPath);
- } else { // delete file
- fs.unlinkSync(curPath);
- }
- });
- fs.rmdirSync(folder_to_delete);
- }
-};
-
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
res.header("Access-Control-Allow-Origin", getOrigin());
@@ -1807,9 +1751,9 @@ const optionalJwt = function (req, res, next) {
const uuid = using_body ? req.body.uuid : req.query.uuid;
const uid = using_body ? req.body.uid : req.query.uid;
const type = using_body ? req.body.type : req.query.type;
- const file = !req.query.id ? auth_api.getUserVideo(uuid, uid, type, true, !!req.body) : auth_api.getUserPlaylist(uuid, req.query.id, null, true);
- const is_shared = file ? file['sharingEnabled'] : false;
- if (is_shared) {
+ const playlist_id = using_body ? req.body.id : req.query.id;
+ const file = !playlist_id ? auth_api.getUserVideo(uuid, uid, type, true, req.body) : auth_api.getUserPlaylist(uuid, playlist_id, null, false);
+ if (file) {
req.can_watch = true;
return next();
} else {
@@ -1891,7 +1835,7 @@ app.post('/api/tomp4', optionalJwt, async function(req, res) {
ui_uid: req.body.ui_uid,
user: req.isAuthenticated() ? req.user.uid : null
}
-
+
const safeDownloadOverride = config_api.getConfigItem('ytdl_safe_download_override') || config_api.globalArgsRequiresSafeDownload();
if (safeDownloadOverride) logger.verbose('Download is running with the safe download override.');
const is_playlist = url.includes('playlist');
@@ -1913,8 +1857,21 @@ app.post('/api/killAllDownloads', optionalJwt, async function(req, res) {
res.send(result_obj);
});
+/**
+ * add thumbnails if present
+ * @param files - List of files with thumbnailPath property.
+ */
+async function addThumbnails(files) {
+ await Promise.all(files.map(async file => {
+ const thumbnailPath = file['thumbnailPath'];
+ if (thumbnailPath && (await fs.pathExists(thumbnailPath))) {
+ file['thumbnailBlob'] = await fs.readFile(thumbnailPath);
+ }
+ }));
+}
+
// gets all download mp3s
-app.get('/api/getMp3s', optionalJwt, function(req, res) {
+app.get('/api/getMp3s', optionalJwt, async function(req, res) {
var mp3s = db.get('files.audio').value(); // getMp3s();
var playlists = db.get('playlists.audio').value();
const is_authenticated = req.isAuthenticated();
@@ -1929,12 +1886,10 @@ app.get('/api/getMp3s', optionalJwt, function(req, res) {
if (config_api.getConfigItem('ytdl_include_thumbnail')) {
// add thumbnails if present
- mp3s.forEach(mp3 => {
- if (mp3['thumbnailPath'] && fs.existsSync(mp3['thumbnailPath']))
- mp3['thumbnailBlob'] = fs.readFileSync(mp3['thumbnailPath']);
- });
+ await addThumbnails(mp3s);
}
+
res.send({
mp3s: mp3s,
playlists: playlists
@@ -1942,7 +1897,7 @@ app.get('/api/getMp3s', optionalJwt, function(req, res) {
});
// gets all download mp4s
-app.get('/api/getMp4s', optionalJwt, function(req, res) {
+app.get('/api/getMp4s', optionalJwt, async function(req, res) {
var mp4s = db.get('files.video').value(); // getMp4s();
var playlists = db.get('playlists.video').value();
@@ -1958,10 +1913,7 @@ app.get('/api/getMp4s', optionalJwt, function(req, res) {
if (config_api.getConfigItem('ytdl_include_thumbnail')) {
// add thumbnails if present
- mp4s.forEach(mp4 => {
- if (mp4['thumbnailPath'] && fs.existsSync(mp4['thumbnailPath']))
- mp4['thumbnailBlob'] = fs.readFileSync(mp4['thumbnailPath']);
- });
+ await addThumbnails(mp4s);
}
res.send({
@@ -2008,7 +1960,7 @@ app.post('/api/getFile', optionalJwt, function (req, res) {
}
});
-app.post('/api/getAllFiles', optionalJwt, function (req, res) {
+app.post('/api/getAllFiles', optionalJwt, async function (req, res) {
// these are returned
let files = [];
let playlists = [];
@@ -2018,7 +1970,7 @@ app.post('/api/getAllFiles', optionalJwt, function (req, res) {
let audios = null;
let audio_playlists = null;
let video_playlists = null;
- let subscriptions = subscriptions_api.getAllSubscriptions(req.isAuthenticated() ? req.user.uid : null);
+ let subscriptions = config_api.getConfigItem('ytdl_allow_subscriptions') ? (subscriptions_api.getAllSubscriptions(req.isAuthenticated() ? req.user.uid : null)) : [];
// get basic info depending on multi-user mode being enabled
if (req.isAuthenticated()) {
@@ -2035,7 +1987,7 @@ app.post('/api/getAllFiles', optionalJwt, function (req, res) {
files = videos.concat(audios);
playlists = video_playlists.concat(audio_playlists);
-
+
// loop through subscriptions and add videos
for (let i = 0; i < subscriptions.length; i++) {
sub = subscriptions[i];
@@ -2052,12 +2004,9 @@ app.post('/api/getAllFiles', optionalJwt, function (req, res) {
if (config_api.getConfigItem('ytdl_include_thumbnail')) {
// add thumbnails if present
- files.forEach(file => {
- if (file['thumbnailPath'] && fs.existsSync(file['thumbnailPath']))
- file['thumbnailBlob'] = fs.readFileSync(file['thumbnailPath']);
- });
+ await addThumbnails(files);
}
-
+
res.send({
files: files,
playlists: playlists
@@ -2315,7 +2264,7 @@ app.post('/api/getSubscription', optionalJwt, async (req, res) => {
let appended_base_path = path.join(base_path, (subscription.isPlaylist ? 'playlists' : 'channels'), subscription.name, '/');
let files;
try {
- files = utils.recFindByExt(appended_base_path, 'mp4');
+ files = await utils.recFindByExt(appended_base_path, 'mp4');
} catch(e) {
files = null;
logger.info('Failed to get folder for subscription: ' + subscription.name + ' at path ' + appended_base_path);
@@ -2539,7 +2488,7 @@ app.post('/api/deleteFile', optionalJwt, async (req, res) => {
var name = file_obj.id;
var fullpath = file_obj ? file_obj.path : null;
var wasDeleted = false;
- if (fs.existsSync(fullpath))
+ if (await fs.pathExists(fullpath))
{
wasDeleted = type === 'audio' ? await deleteAudioFile(name, path.basename(fullpath), blacklistMode) : await deleteVideoFile(name, path.basename(fullpath), blacklistMode);
db.get('files.video').remove({uid: uid}).write();
@@ -2573,9 +2522,9 @@ app.post('/api/downloadFile', optionalJwt, async (req, res) => {
let base_path = fileFolderPath;
let usersFileFolder = null;
const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode');
- if (multiUserMode && req.body.uuid) {
+ if (multiUserMode && (req.body.uuid || req.user.uid)) {
usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
- base_path = path.join(usersFileFolder, req.body.uuid, type);
+ base_path = path.join(usersFileFolder, req.body.uuid ? req.body.uuid : req.user.uid, type);
}
if (!subscriptionName) {
file = path.join(__dirname, base_path, fileNames + ext);
@@ -2592,7 +2541,8 @@ app.post('/api/downloadFile', optionalJwt, async (req, res) => {
for (let i = 0; i < fileNames.length; i++) {
fileNames[i] = decodeURIComponent(fileNames[i]);
}
- file = await createPlaylistZipFile(fileNames, type, outputName, fullPathProvided);
+ file = await createPlaylistZipFile(fileNames, type, outputName, fullPathProvided, req.body.uuid);
+ if (!path.isAbsolute(file)) file = path.join(__dirname, file);
}
res.sendFile(file, function (err) {
if (err) {
@@ -2613,7 +2563,7 @@ app.post('/api/downloadArchive', async (req, res) => {
let full_archive_path = path.join(archive_dir, 'archive.txt');
- if (fs.existsSync(full_archive_path)) {
+ if (await fs.pathExists(full_archive_path)) {
res.sendFile(full_archive_path);
} else {
res.sendStatus(404);
@@ -2625,14 +2575,14 @@ var upload_multer = multer({ dest: __dirname + '/appdata/' });
app.post('/api/uploadCookies', upload_multer.single('cookies'), async (req, res) => {
const new_path = path.join(__dirname, 'appdata', 'cookies.txt');
- if (fs.existsSync(req.file.path)) {
- fs.renameSync(req.file.path, new_path);
+ if (await fs.pathExists(req.file.path)) {
+ await fs.rename(req.file.path, new_path);
} else {
res.sendStatus(500);
return;
}
- if (fs.existsSync(new_path)) {
+ if (await fs.pathExists(new_path)) {
res.send({success: true});
} else {
res.sendStatus(500);
@@ -2806,9 +2756,9 @@ app.post('/api/logs', async function(req, res) {
let logs = null;
let lines = req.body.lines;
logs_path = path.join('appdata', 'logs', 'combined.log')
- if (fs.existsSync(logs_path)) {
+ if (await fs.pathExists(logs_path)) {
if (lines) logs = await read_last_lines.read(logs_path, lines);
- else logs = fs.readFileSync(logs_path, 'utf8');
+ else logs = await fs.readFile(logs_path, 'utf8');
}
else
logger.error(`Failed to find logs file at the expected location: ${logs_path}`)
@@ -2824,8 +2774,10 @@ app.post('/api/clearAllLogs', async function(req, res) {
logs_err_path = path.join('appdata', 'logs', 'error.log');
let success = false;
try {
- fs.writeFileSync(logs_path, '');
- fs.writeFileSync(logs_err_path, '');
+ await Promise.all([
+ fs.writeFile(logs_path, ''),
+ fs.writeFile(logs_err_path, '')
+ ])
success = true;
} catch(e) {
logger.error(e);
@@ -2842,10 +2794,8 @@ app.post('/api/clearAllLogs', async function(req, res) {
let type = req.body.type;
let result = null;
if (!urlMode) {
- if (type === 'audio') {
- result = getAudioInfos(fileNames)
- } else if (type === 'video') {
- result = getVideoInfos(fileNames);
+ if (type === 'audio' || type === 'video') {
+ result = await getAudioOrVideoInfos(type, fileNames);
}
} else {
result = await getUrlInfos(fileNames);
@@ -2918,7 +2868,7 @@ app.post('/api/deleteUser', optionalJwt, async (req, res) => {
const user_db_obj = users_db.get('users').find({uid: uid});
if (user_db_obj.value()) {
// user exists, let's delete
- deleteFolderRecursive(user_folder);
+ await fs.remove(user_folder);
users_db.get('users').remove({uid: uid}).write();
}
res.send({success: true});
diff --git a/backend/authentication/auth.js b/backend/authentication/auth.js
index a64ca19..f16fbf1 100644
--- a/backend/authentication/auth.js
+++ b/backend/authentication/auth.js
@@ -139,12 +139,12 @@ exports.registerUser = function(req, res) {
exports.passport.use(new LocalStrategy({
usernameField: 'username',
passwordField: 'password'},
- function(username, password, done) {
+ async function(username, password, done) {
const user = users_db.get('users').find({name: username}).value();
if (!user) { logger.error(`User ${username} not found`); return done(null, false); }
if (user.auth_method && user.auth_method !== 'internal') { return done(null, false); }
if (user) {
- return done(null, bcrypt.compareSync(password, user.passhash) ? user : false);
+ return done(null, (await bcrypt.compare(password, user.passhash)) ? user : false);
}
}
));
@@ -160,7 +160,7 @@ exports.passport.use(new LdapStrategy(getLDAPConfiguration,
// check if ldap auth is enabled
const ldap_enabled = config_api.getConfigItem('ytdl_auth_method') === 'ldap';
if (!ldap_enabled) return done(null, false);
-
+
const user_uid = user.uid;
let db_user = users_db.get('users').find({uid: user_uid}).value();
if (!db_user) {
@@ -226,15 +226,13 @@ exports.ensureAuthenticatedElseError = function(req, res, next) {
// change password
exports.changeUserPassword = async function(user_uid, new_pass) {
- return new Promise(resolve => {
- bcrypt.hash(new_pass, saltRounds)
- .then(function(hash) {
- users_db.get('users').find({uid: user_uid}).assign({passhash: hash}).write();
- resolve(true);
- }).catch(err => {
- resolve(false);
- });
- });
+ try {
+ const hash = await bcrypt.hash(new_pass, saltRounds);
+ users_db.get('users').find({uid: user_uid}).assign({passhash: hash}).write();
+ return true;
+ } catch (err) {
+ return false;
+ }
}
// change user permissions
@@ -283,6 +281,7 @@ exports.getUserVideos = function(user_uid, type) {
}
exports.getUserVideo = function(user_uid, file_uid, type, requireSharing = false) {
+ let file = null;
if (!type) {
file = users_db.get('users').find({uid: user_uid}).get(`files.audio`).find({uid: file_uid}).value();
if (!file) {
@@ -296,7 +295,7 @@ exports.getUserVideo = function(user_uid, file_uid, type, requireSharing = false
if (!file && type) file = users_db.get('users').find({uid: user_uid}).get(`files.${type}`).find({uid: file_uid}).value();
// prevent unauthorized users from accessing the file info
- if (requireSharing && !file['sharingEnabled']) file = null;
+ if (file && !file['sharingEnabled'] && requireSharing) file = null;
return file;
}
@@ -351,7 +350,7 @@ exports.registerUserFile = function(user_uid, file_object, type) {
.write();
}
-exports.deleteUserFile = function(user_uid, file_uid, type, blacklistMode = false) {
+exports.deleteUserFile = async function(user_uid, file_uid, type, blacklistMode = false) {
let success = false;
const file_obj = users_db.get('users').find({uid: user_uid}).get(`files.${type}`).find({uid: file_uid}).value();
if (file_obj) {
@@ -374,20 +373,20 @@ exports.deleteUserFile = function(user_uid, file_uid, type, blacklistMode = fals
.remove({
uid: file_uid
}).write();
- if (fs.existsSync(full_path)) {
+ if (await fs.pathExists(full_path)) {
// remove json and file
const json_path = path.join(usersFileFolder, user_uid, type, file_obj.id + '.info.json');
const alternate_json_path = path.join(usersFileFolder, user_uid, type, file_obj.id + ext + '.info.json');
let youtube_id = null;
- if (fs.existsSync(json_path)) {
- youtube_id = fs.readJSONSync(json_path).id;
- fs.unlinkSync(json_path);
- } else if (fs.existsSync(alternate_json_path)) {
- youtube_id = fs.readJSONSync(alternate_json_path).id;
- fs.unlinkSync(alternate_json_path);
+ if (await fs.pathExists(json_path)) {
+ youtube_id = await fs.readJSON(json_path).id;
+ await fs.unlink(json_path);
+ } else if (await fs.pathExists(alternate_json_path)) {
+ youtube_id = await fs.readJSON(alternate_json_path).id;
+ await fs.unlink(alternate_json_path);
}
- fs.unlinkSync(full_path);
+ await fs.unlink(full_path);
// do archive stuff
@@ -396,17 +395,17 @@ exports.deleteUserFile = function(user_uid, file_uid, type, blacklistMode = fals
const archive_path = path.join(usersFileFolder, user_uid, 'archives', `archive_${type}.txt`);
// use subscriptions API to remove video from the archive file, and write it to the blacklist
- if (fs.existsSync(archive_path)) {
- const line = youtube_id ? subscriptions_api.removeIDFromArchive(archive_path, youtube_id) : null;
+ if (await fs.pathExists(archive_path)) {
+ const line = youtube_id ? await subscriptions_api.removeIDFromArchive(archive_path, youtube_id) : null;
if (blacklistMode && line) {
let blacklistPath = path.join(usersFileFolder, user_uid, 'archives', `blacklist_${type}.txt`);
// adds newline to the beginning of the line
line = '\n' + line;
- fs.appendFileSync(blacklistPath, line);
+ await fs.appendFile(blacklistPath, line);
}
} else {
logger.info(`Could not find archive file for ${type} files. Creating...`);
- fs.ensureFileSync(archive_path);
+ await fs.ensureFile(archive_path);
}
}
}
@@ -532,7 +531,7 @@ function generateUserObject(userid, username, hash, auth_method = 'internal') {
role: userid === 'admin' && auth_method === 'internal' ? 'admin' : 'user',
permissions: [],
permission_overrides: [],
- auth_method: auth_method
+ auth_method: auth_method
};
return new_user;
}
diff --git a/backend/db.js b/backend/db.js
index f625807..a2ddb7a 100644
--- a/backend/db.js
+++ b/backend/db.js
@@ -182,9 +182,9 @@ async function importUnregisteredFiles() {
}
// run through check list and check each file to see if it's missing from the db
- dirs_to_check.forEach(dir_to_check => {
+ for (const dir_to_check of dirs_to_check) {
// recursively get all files in dir's path
- const files = utils.getDownloadedFilesByType(dir_to_check.basePath, dir_to_check.type);
+ const files = await utils.getDownloadedFilesByType(dir_to_check.basePath, dir_to_check.type);
files.forEach(file => {
// check if file exists in db, if not add it
@@ -195,7 +195,7 @@ async function importUnregisteredFiles() {
logger.verbose(`Added discovered file to the database: ${file.id}`);
}
});
- });
+ }
}
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 1e4f7ab..c969112 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -1968,9 +1968,9 @@
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"node-fetch": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
- "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
+ "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
},
"node-id3": {
"version": "0.1.16",
diff --git a/backend/package.json b/backend/package.json
index b153a5d..91a1777 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -43,7 +43,7 @@
"md5": "^2.2.1",
"merge-files": "^0.1.2",
"multer": "^1.4.2",
- "node-fetch": "^2.6.0",
+ "node-fetch": "^2.6.1",
"node-id3": "^0.1.14",
"nodemon": "^2.0.2",
"passport": "^0.4.1",
diff --git a/backend/subscriptions.js b/backend/subscriptions.js
index f1c20a7..5eafc4e 100644
--- a/backend/subscriptions.js
+++ b/backend/subscriptions.js
@@ -79,17 +79,18 @@ async function getSubscriptionInfo(sub, user_uid = null) {
else
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
- return new Promise(resolve => {
- // get videos
- let downloadConfig = ['--dump-json', '--playlist-end', '1'];
- let useCookies = config_api.getConfigItem('ytdl_use_cookies');
- if (useCookies) {
- if (fs.existsSync(path.join(__dirname, 'appdata', 'cookies.txt'))) {
- downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
- } else {
- logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
- }
+ // get videos
+ let downloadConfig = ['--dump-json', '--playlist-end', '1'];
+ let useCookies = config_api.getConfigItem('ytdl_use_cookies');
+ if (useCookies) {
+ if (await fs.pathExists(path.join(__dirname, 'appdata', 'cookies.txt'))) {
+ downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
+ } else {
+ logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
}
+ }
+
+ return new Promise(resolve => {
youtubedl.exec(sub.url, downloadConfig, {}, function(err, output) {
if (debugMode) {
logger.info('Subscribe: got info for subscription ' + sub.id);
@@ -152,39 +153,36 @@ async function getSubscriptionInfo(sub, user_uid = null) {
}
async function unsubscribe(sub, deleteMode, user_uid = null) {
- return new Promise(async resolve => {
- let basePath = null;
- if (user_uid)
- basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
- else
- basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
- let result_obj = { success: false, error: '' };
+ let basePath = null;
+ if (user_uid)
+ basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
+ else
+ basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
+ let result_obj = { success: false, error: '' };
- let id = sub.id;
- if (user_uid)
- users_db.get('users').find({uid: user_uid}).get('subscriptions').remove({id: id}).write();
- else
- db.get('subscriptions').remove({id: id}).write();
+ let id = sub.id;
+ if (user_uid)
+ users_db.get('users').find({uid: user_uid}).get('subscriptions').remove({id: id}).write();
+ else
+ db.get('subscriptions').remove({id: id}).write();
- // failed subs have no name, on unsubscribe they shouldn't error
- if (!sub.name) {
- return;
- }
+ // failed subs have no name, on unsubscribe they shouldn't error
+ if (!sub.name) {
+ return;
+ }
- const appendedBasePath = getAppendedBasePath(sub, basePath);
- if (deleteMode && fs.existsSync(appendedBasePath)) {
- if (sub.archive && fs.existsSync(sub.archive)) {
- const archive_file_path = path.join(sub.archive, 'archive.txt');
- // deletes archive if it exists
- if (fs.existsSync(archive_file_path)) {
- fs.unlinkSync(archive_file_path);
- }
- fs.rmdirSync(sub.archive);
+ const appendedBasePath = getAppendedBasePath(sub, basePath);
+ if (deleteMode && (await fs.pathExists(appendedBasePath))) {
+ if (sub.archive && (await fs.pathExists(sub.archive))) {
+ const archive_file_path = path.join(sub.archive, 'archive.txt');
+ // deletes archive if it exists
+ if (await fs.pathExists(archive_file_path)) {
+ await fs.unlink(archive_file_path);
}
- deleteFolderRecursive(appendedBasePath);
+ await fs.rmdir(sub.archive);
}
- });
-
+ await fs.remove(appendedBasePath);
+ }
}
async function deleteSubscriptionFile(sub, file, deleteForever, file_uid = null, user_uid = null) {
@@ -202,155 +200,154 @@ async function deleteSubscriptionFile(sub, file, deleteForever, file_uid = null,
const name = file;
let retrievedID = null;
sub_db.get('videos').remove({uid: file_uid}).write();
- return new Promise(resolve => {
- let filePath = appendedBasePath;
- const ext = (sub.type && sub.type === 'audio') ? '.mp3' : '.mp4'
- var jsonPath = path.join(__dirname,filePath,name+'.info.json');
- var videoFilePath = path.join(__dirname,filePath,name+ext);
- var imageFilePath = path.join(__dirname,filePath,name+'.jpg');
- var altImageFilePath = path.join(__dirname,filePath,name+'.jpg');
- jsonExists = fs.existsSync(jsonPath);
- videoFileExists = fs.existsSync(videoFilePath);
- imageFileExists = fs.existsSync(imageFilePath);
- altImageFileExists = fs.existsSync(altImageFilePath);
+ let filePath = appendedBasePath;
+ const ext = (sub.type && sub.type === 'audio') ? '.mp3' : '.mp4'
+ var jsonPath = path.join(__dirname,filePath,name+'.info.json');
+ var videoFilePath = path.join(__dirname,filePath,name+ext);
+ var imageFilePath = path.join(__dirname,filePath,name+'.jpg');
+ var altImageFilePath = path.join(__dirname,filePath,name+'.jpg');
- if (jsonExists) {
- retrievedID = JSON.parse(fs.readFileSync(jsonPath, 'utf8'))['id'];
- fs.unlinkSync(jsonPath);
- }
+ const [jsonExists, videoFileExists, imageFileExists, altImageFileExists] = await Promise.all([
+ fs.pathExists(jsonPath),
+ fs.pathExists(videoFilePath),
+ fs.pathExists(imageFilePath),
+ fs.pathExists(altImageFilePath),
+ ]);
- if (imageFileExists) {
- fs.unlinkSync(imageFilePath);
- }
+ if (jsonExists) {
+ retrievedID = JSON.parse(await fs.readFile(jsonPath, 'utf8'))['id'];
+ await fs.unlink(jsonPath);
+ }
- if (altImageFileExists) {
- fs.unlinkSync(altImageFilePath);
- }
+ if (imageFileExists) {
+ await fs.unlink(imageFilePath);
+ }
- if (videoFileExists) {
- fs.unlink(videoFilePath, function(err) {
- if (fs.existsSync(jsonPath) || fs.existsSync(videoFilePath)) {
- resolve(false);
- } else {
- // check if the user wants the video to be redownloaded (deleteForever === false)
- if (!deleteForever && useArchive && sub.archive && retrievedID) {
- const archive_path = path.join(sub.archive, 'archive.txt')
- // if archive exists, remove line with video ID
- if (fs.existsSync(archive_path)) {
- removeIDFromArchive(archive_path, retrievedID);
- }
- }
- resolve(true);
- }
- });
+ if (altImageFileExists) {
+ await fs.unlink(altImageFilePath);
+ }
+
+ if (videoFileExists) {
+ await fs.unlink(videoFilePath);
+ if ((await fs.pathExists(jsonPath)) || (await fs.pathExists(videoFilePath))) {
+ return false;
} else {
- // TODO: tell user that the file didn't exist
- resolve(true);
+ // check if the user wants the video to be redownloaded (deleteForever === false)
+ if (!deleteForever && useArchive && sub.archive && retrievedID) {
+ const archive_path = path.join(sub.archive, 'archive.txt')
+ // if archive exists, remove line with video ID
+ if (await fs.pathExists(archive_path)) {
+ await removeIDFromArchive(archive_path, retrievedID);
+ }
+ }
+ return true;
}
-
- });
+ } else {
+ // TODO: tell user that the file didn't exist
+ return true;
+ }
}
async function getVideosForSub(sub, user_uid = null) {
- return new Promise(resolve => {
- if (!subExists(sub.id, user_uid)) {
- resolve(false);
- return;
+ if (!subExists(sub.id, user_uid)) {
+ return false;
+ }
+
+ // get sub_db
+ let sub_db = null;
+ if (user_uid)
+ sub_db = users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id});
+ else
+ sub_db = db.get('subscriptions').find({id: sub.id});
+
+ // get basePath
+ let basePath = null;
+ if (user_uid)
+ basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
+ else
+ basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
+
+ const useArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
+
+ let appendedBasePath = null
+ appendedBasePath = getAppendedBasePath(sub, basePath);
+
+ let multiUserMode = null;
+ if (user_uid) {
+ multiUserMode = {
+ user: user_uid,
+ file_path: appendedBasePath
}
+ }
- // get sub_db
- let sub_db = null;
- if (user_uid)
- sub_db = users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id});
- else
- sub_db = db.get('subscriptions').find({id: sub.id});
+ const ext = (sub.type && sub.type === 'audio') ? '.mp3' : '.mp4'
- // get basePath
- let basePath = null;
- if (user_uid)
- basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
- else
- basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
+ let fullOutput = `${appendedBasePath}/%(title)s.%(ext)s`;
+ if (sub.custom_output) {
+ fullOutput = `${appendedBasePath}/${sub.custom_output}.%(ext)s`;
+ }
- const useArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
+ let downloadConfig = ['-o', fullOutput, '-ciw', '--write-info-json', '--print-json'];
- let appendedBasePath = null
- appendedBasePath = getAppendedBasePath(sub, basePath);
+ let qualityPath = null;
+ if (sub.type && sub.type === 'audio') {
+ qualityPath = ['-f', 'bestaudio']
+ qualityPath.push('-x');
+ qualityPath.push('--audio-format', 'mp3');
+ } else {
+ qualityPath = ['-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4']
+ }
- let multiUserMode = null;
- if (user_uid) {
- multiUserMode = {
- user: user_uid,
- file_path: appendedBasePath
- }
+ downloadConfig.push(...qualityPath)
+
+ if (sub.custom_args) {
+ customArgsArray = sub.custom_args.split(',,');
+ if (customArgsArray.indexOf('-f') !== -1) {
+ // if custom args has a custom quality, replce the original quality with that of custom args
+ const original_output_index = downloadConfig.indexOf('-f');
+ downloadConfig.splice(original_output_index, 2);
}
+ downloadConfig.push(...customArgsArray);
+ }
- const ext = (sub.type && sub.type === 'audio') ? '.mp3' : '.mp4'
+ let archive_dir = null;
+ let archive_path = null;
- let fullOutput = appendedBasePath + '/%(title)s' + ext;
- if (sub.custom_output) {
- fullOutput = appendedBasePath + '/' + sub.custom_output + ext;
+ if (useArchive) {
+ if (sub.archive) {
+ archive_dir = sub.archive;
+ archive_path = path.join(archive_dir, 'archive.txt')
}
+ downloadConfig.push('--download-archive', archive_path);
+ }
- let downloadConfig = ['-o', fullOutput, '-ciw', '--write-info-json', '--print-json'];
+ // if streaming only mode, just get the list of videos
+ if (sub.streamingOnly) {
+ downloadConfig = ['-f', 'best', '--dump-json'];
+ }
- let qualityPath = null;
- if (sub.type && sub.type === 'audio') {
- qualityPath = ['-f', 'bestaudio']
- qualityPath.push('-x');
- qualityPath.push('--audio-format', 'mp3');
+ if (sub.timerange) {
+ downloadConfig.push('--dateafter', sub.timerange);
+ }
+
+ let useCookies = config_api.getConfigItem('ytdl_use_cookies');
+ if (useCookies) {
+ if (await fs.pathExists(path.join(__dirname, 'appdata', 'cookies.txt'))) {
+ downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
} else {
- qualityPath = ['-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4']
+ logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
}
+ }
- downloadConfig.push(...qualityPath)
+ if (config_api.getConfigItem('ytdl_include_thumbnail')) {
+ downloadConfig.push('--write-thumbnail');
+ }
- if (sub.custom_args) {
- customArgsArray = sub.custom_args.split(',,');
- if (customArgsArray.indexOf('-f') !== -1) {
- // if custom args has a custom quality, replce the original quality with that of custom args
- const original_output_index = downloadConfig.indexOf('-f');
- downloadConfig.splice(original_output_index, 2);
- }
- downloadConfig.push(...customArgsArray);
- }
+ // get videos
+ logger.verbose('Subscription: getting videos for subscription ' + sub.name);
- let archive_dir = null;
- let archive_path = null;
-
- if (useArchive) {
- if (sub.archive) {
- archive_dir = sub.archive;
- archive_path = path.join(archive_dir, 'archive.txt')
- }
- downloadConfig.push('--download-archive', archive_path);
- }
-
- // if streaming only mode, just get the list of videos
- if (sub.streamingOnly) {
- downloadConfig = ['-f', 'best', '--dump-json'];
- }
-
- if (sub.timerange) {
- downloadConfig.push('--dateafter', sub.timerange);
- }
-
- let useCookies = config_api.getConfigItem('ytdl_use_cookies');
- if (useCookies) {
- if (fs.existsSync(path.join(__dirname, 'appdata', 'cookies.txt'))) {
- downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
- } else {
- logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
- }
- }
-
- if (config_api.getConfigItem('ytdl_include_thumbnail')) {
- downloadConfig.push('--write-thumbnail');
- }
-
- // get videos
- logger.verbose('Subscription: getting videos for subscription ' + sub.name);
+ return new Promise(resolve => {
youtubedl.exec(sub.url, downloadConfig, {}, function(err, output) {
logger.verbose('Subscription: finished check for ' + sub.name);
if (err && !output) {
@@ -463,23 +460,8 @@ function getAppendedBasePath(sub, base_path) {
return path.join(base_path, (sub.isPlaylist ? 'playlists/' : 'channels/'), sub.name);
}
-// https://stackoverflow.com/a/32197381/8088021
-const deleteFolderRecursive = function(folder_to_delete) {
- if (fs.existsSync(folder_to_delete)) {
- fs.readdirSync(folder_to_delete).forEach((file, index) => {
- const curPath = path.join(folder_to_delete, file);
- if (fs.lstatSync(curPath).isDirectory()) { // recurse
- deleteFolderRecursive(curPath);
- } else { // delete file
- fs.unlinkSync(curPath);
- }
- });
- fs.rmdirSync(folder_to_delete);
- }
- };
-
-function removeIDFromArchive(archive_path, id) {
- let data = fs.readFileSync(archive_path, {encoding: 'utf-8'});
+async function removeIDFromArchive(archive_path, id) {
+ let data = await fs.readFile(archive_path, {encoding: 'utf-8'});
if (!data) {
logger.error('Archive could not be found.');
return;
@@ -500,7 +482,7 @@ function removeIDFromArchive(archive_path, id) {
// UPDATE FILE WITH NEW DATA
const updatedData = dataArray.join('\n');
- fs.writeFileSync(archive_path, updatedData);
+ await fs.writeFile(archive_path, updatedData);
if (line) return line;
if (err) throw err;
}
diff --git a/backend/utils.js b/backend/utils.js
index efedb3b..411a7da 100644
--- a/backend/utils.js
+++ b/backend/utils.js
@@ -4,6 +4,7 @@ const config_api = require('./config');
const is_windows = process.platform === 'win32';
+// replaces .webm with appropriate extension
function getTrueFileName(unfixed_path, type) {
let fixed_path = unfixed_path;
@@ -19,21 +20,21 @@ function getTrueFileName(unfixed_path, type) {
return fixed_path;
}
-function getDownloadedFilesByType(basePath, type) {
+async function getDownloadedFilesByType(basePath, type) {
// return empty array if the path doesn't exist
- if (!fs.existsSync(basePath)) return [];
+ if (!(await fs.pathExists(basePath))) return [];
let files = [];
const ext = type === 'audio' ? 'mp3' : 'mp4';
- var located_files = recFindByExt(basePath, ext);
+ var located_files = await recFindByExt(basePath, ext);
for (let i = 0; i < located_files.length; i++) {
let file = located_files[i];
- var file_path = path.basename(file);
+ var file_path = file.substring(basePath.includes('\\') ? basePath.length+1 : basePath.length, file.length);
- var stats = fs.statSync(file);
+ var stats = await fs.stat(file);
var id = file_path.substring(0, file_path.length-4);
- var jsonobj = getJSONByType(type, id, basePath);
+ var jsonobj = await getJSONByType(type, id, basePath);
if (!jsonobj) continue;
var title = jsonobj.title;
var url = jsonobj.webpage_url;
@@ -129,7 +130,7 @@ function fixVideoMetadataPerms(name, type, customPath = null) {
: config_api.getConfigItem('ytdl_video_folder_path');
const ext = type === 'audio' ? '.mp3' : '.mp4';
-
+
const files_to_fix = [
// JSONs
path.join(customPath, name + '.info.json'),
@@ -158,27 +159,25 @@ function deleteJSONFile(name, type, customPath = null) {
}
-function recFindByExt(base,ext,files,result)
+async function recFindByExt(base,ext,files,result)
{
- files = files || fs.readdirSync(base)
+ files = files || (await fs.readdir(base))
result = result || []
- files.forEach(
- function (file) {
- var newbase = path.join(base,file)
- if ( fs.statSync(newbase).isDirectory() )
+ for (const file of files) {
+ var newbase = path.join(base,file)
+ if ( (await fs.stat(newbase)).isDirectory() )
+ {
+ result = await recFindByExt(newbase,ext,await fs.readdir(newbase),result)
+ }
+ else
+ {
+ if ( file.substr(-1*(ext.length+1)) == '.' + ext )
{
- result = recFindByExt(newbase,ext,fs.readdirSync(newbase),result)
- }
- else
- {
- if ( file.substr(-1*(ext.length+1)) == '.' + ext )
- {
- result.push(newbase)
- }
+ result.push(newbase)
}
}
- )
+ }
return result
}
diff --git a/src/app/app.component.html b/src/app/app.component.html
index d49c48e..4d8f2a7 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -46,7 +46,7 @@
NOTE: Uploading new cookies will overrride your previous cookies. Also note that cookies are instance-wide, not per-user.
+NOTE: Uploading new cookies will override your previous cookies. Also note that cookies are instance-wide, not per-user.