mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-04-24 23:53:20 +03:00
Compare commits
12 Commits
python3-do
...
electron-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c789ba9553 | ||
|
|
b8e1117ff6 | ||
|
|
b64a001ae1 | ||
|
|
c0a385ce78 | ||
|
|
258d5ff495 | ||
|
|
fb5c13db27 | ||
|
|
92413bd360 | ||
|
|
7174ef5f57 | ||
|
|
7ff906fd35 | ||
|
|
6e084bd94a | ||
|
|
21b97911e8 | ||
|
|
117255b0b7 |
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: "[BUG]"
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Environment**
|
||||||
|
- YoutubeDL-Material version
|
||||||
|
- Docker tag: <tag> (optional)
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: "[FEATURE]"
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
138
backend/app.js
138
backend/app.js
@@ -28,12 +28,22 @@ const { spawn } = require('child_process')
|
|||||||
const read_last_lines = require('read-last-lines');
|
const read_last_lines = require('read-last-lines');
|
||||||
var ps = require('ps-node');
|
var ps = require('ps-node');
|
||||||
|
|
||||||
// needed if bin/details somehow gets deleted
|
const using_electron = process.versions['electron'];
|
||||||
const DETAILS_BIN_PATH = 'node_modules/youtube-dl/bin/details'
|
|
||||||
if (!fs.existsSync(DETAILS_BIN_PATH)) fs.writeJSONSync(DETAILS_BIN_PATH, {"version":"2000.06.06","path":"node_modules\\youtube-dl\\bin\\youtube-dl.exe","exec":"youtube-dl.exe","downloader":"youtube-dl"})
|
// youtube-dl consts
|
||||||
|
const YOUTUBE_DL_PATH = using_electron ? 'youtube-dl' : 'node_modules/youtube-dl/bin';
|
||||||
|
const DETAILS_BIN_PATH = path.join(YOUTUBE_DL_PATH, 'details');
|
||||||
|
const DEFAULT_BIN_JSON = {"version":"2000.06.06","path": path.join(YOUTUBE_DL_PATH, "youtube-dl.exe"),"exec":"youtube-dl.exe","downloader":"youtube-dl"};
|
||||||
|
if (!fs.existsSync(DETAILS_BIN_PATH)) fs.writeJSONSync(DETAILS_BIN_PATH, DEFAULT_BIN_JSON);
|
||||||
|
|
||||||
var youtubedl = require('youtube-dl');
|
var youtubedl = require('youtube-dl');
|
||||||
|
|
||||||
|
if (using_electron) {
|
||||||
|
youtubedl.setYtdlBinary(YOUTUBE_DL_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// local APIs
|
||||||
|
|
||||||
var config_api = require('./config.js');
|
var config_api = require('./config.js');
|
||||||
var subscriptions_api = require('./subscriptions')
|
var subscriptions_api = require('./subscriptions')
|
||||||
var categories_api = require('./categories');
|
var categories_api = require('./categories');
|
||||||
@@ -136,8 +146,7 @@ var downloadOnlyMode = null;
|
|||||||
var useDefaultDownloadingAgent = null;
|
var useDefaultDownloadingAgent = null;
|
||||||
var customDownloadingAgent = null;
|
var customDownloadingAgent = null;
|
||||||
var allowSubscriptions = null;
|
var allowSubscriptions = null;
|
||||||
var subscriptionsCheckInterval = null;
|
var archivePath = path.join('appdata', 'archives');
|
||||||
var archivePath = path.join(__dirname, 'appdata', 'archives');
|
|
||||||
|
|
||||||
// other needed values
|
// other needed values
|
||||||
var url_domain = null;
|
var url_domain = null;
|
||||||
@@ -318,9 +327,14 @@ async function startServer() {
|
|||||||
await setPortItemFromENV();
|
await setPortItemFromENV();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.listen(backendPort,function(){
|
const server = app.listen(backendPort,function(){
|
||||||
logger.info(`YoutubeDL-Material ${CONSTS['CURRENT_VERSION']} started on PORT ${backendPort}`);
|
logger.info(`YoutubeDL-Material ${CONSTS['CURRENT_VERSION']} started on PORT ${backendPort}`);
|
||||||
});
|
});
|
||||||
|
process.on('uncaughtException', err => {
|
||||||
|
logger.error('Server has crashed!');
|
||||||
|
logger.error(err);
|
||||||
|
server.close();
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function restartServer(is_update = false) {
|
async function restartServer(is_update = false) {
|
||||||
@@ -390,8 +404,8 @@ async function downloadReleaseFiles(tag) {
|
|||||||
await downloadReleaseZip(tag);
|
await downloadReleaseZip(tag);
|
||||||
|
|
||||||
// deletes contents of public dir
|
// deletes contents of public dir
|
||||||
fs.removeSync(path.join(__dirname, 'public'));
|
fs.removeSync(path.join('public'));
|
||||||
fs.mkdirSync(path.join(__dirname, 'public'));
|
fs.mkdirSync(path.join('public'));
|
||||||
|
|
||||||
let replace_ignore_list = ['youtubedl-material/appdata/default.json',
|
let replace_ignore_list = ['youtubedl-material/appdata/default.json',
|
||||||
'youtubedl-material/appdata/db.json',
|
'youtubedl-material/appdata/db.json',
|
||||||
@@ -400,7 +414,7 @@ async function downloadReleaseFiles(tag) {
|
|||||||
logger.info(`Installing update ${tag}...`)
|
logger.info(`Installing update ${tag}...`)
|
||||||
|
|
||||||
// downloads new package.json and adds new public dir files from the downloaded zip
|
// downloads new package.json and adds new public dir files from the downloaded zip
|
||||||
fs.createReadStream(path.join(__dirname, `youtubedl-material-release-${tag}.zip`)).pipe(unzipper.Parse())
|
fs.createReadStream(path.join(`youtubedl-material-release-${tag}.zip`)).pipe(unzipper.Parse())
|
||||||
.on('entry', function (entry) {
|
.on('entry', function (entry) {
|
||||||
var fileName = entry.path;
|
var fileName = entry.path;
|
||||||
var type = entry.type; // 'Directory' or 'File'
|
var type = entry.type; // 'Directory' or 'File'
|
||||||
@@ -410,8 +424,8 @@ async function downloadReleaseFiles(tag) {
|
|||||||
// get public folder files
|
// get public folder files
|
||||||
var actualFileName = fileName.replace('youtubedl-material/public/', '');
|
var actualFileName = fileName.replace('youtubedl-material/public/', '');
|
||||||
if (actualFileName.length !== 0 && actualFileName.substring(actualFileName.length-1, actualFileName.length) !== '/') {
|
if (actualFileName.length !== 0 && actualFileName.substring(actualFileName.length-1, actualFileName.length) !== '/') {
|
||||||
fs.ensureDirSync(path.join(__dirname, 'public', path.dirname(actualFileName)));
|
fs.ensureDirSync(path.join('public', path.dirname(actualFileName)));
|
||||||
entry.pipe(fs.createWriteStream(path.join(__dirname, 'public', actualFileName)));
|
entry.pipe(fs.createWriteStream(path.join('public', actualFileName)));
|
||||||
} else {
|
} else {
|
||||||
entry.autodrain();
|
entry.autodrain();
|
||||||
}
|
}
|
||||||
@@ -419,7 +433,7 @@ async function downloadReleaseFiles(tag) {
|
|||||||
// get package.json
|
// get package.json
|
||||||
var actualFileName = fileName.replace('youtubedl-material/', '');
|
var actualFileName = fileName.replace('youtubedl-material/', '');
|
||||||
logger.verbose('Downloading file ' + actualFileName);
|
logger.verbose('Downloading file ' + actualFileName);
|
||||||
entry.pipe(fs.createWriteStream(path.join(__dirname, actualFileName)));
|
entry.pipe(fs.createWriteStream(path.join(actualFileName)));
|
||||||
} else {
|
} else {
|
||||||
entry.autodrain();
|
entry.autodrain();
|
||||||
}
|
}
|
||||||
@@ -465,7 +479,7 @@ async function downloadReleaseZip(tag) {
|
|||||||
const tag_without_v = tag.substring(1, tag.length);
|
const tag_without_v = tag.substring(1, tag.length);
|
||||||
const zip_file_name = `youtubedl-material-${tag_without_v}.zip`
|
const zip_file_name = `youtubedl-material-${tag_without_v}.zip`
|
||||||
const latest_zip_link = latest_release_link + zip_file_name;
|
const latest_zip_link = latest_release_link + zip_file_name;
|
||||||
let output_path = path.join(__dirname, `youtubedl-material-release-${tag}.zip`);
|
let output_path = path.join(`youtubedl-material-release-${tag}.zip`);
|
||||||
|
|
||||||
// download zip from release
|
// download zip from release
|
||||||
await fetchFile(latest_zip_link, output_path, 'update ' + tag);
|
await fetchFile(latest_zip_link, output_path, 'update ' + tag);
|
||||||
@@ -483,10 +497,10 @@ async function installDependencies() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function backupServerLite() {
|
async function backupServerLite() {
|
||||||
await fs.ensureDir(path.join(__dirname, 'appdata', 'backups'));
|
await fs.ensureDir(path.join('appdata', 'backups'));
|
||||||
let output_path = path.join('appdata', 'backups', `backup-${Date.now()}.zip`);
|
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.`);
|
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));
|
let output = fs.createWriteStream(path.join(output_path));
|
||||||
|
|
||||||
await new Promise(resolve => {
|
await new Promise(resolve => {
|
||||||
var archive = archiver('zip', {
|
var archive = archiver('zip', {
|
||||||
@@ -627,10 +641,13 @@ async function loadConfig() {
|
|||||||
let subscriptions = await subscriptions_api.getAllSubscriptions();
|
let subscriptions = await subscriptions_api.getAllSubscriptions();
|
||||||
subscriptions_api.updateSubscriptionPropertyMultiple(subscriptions, {downloading: false});
|
subscriptions_api.updateSubscriptionPropertyMultiple(subscriptions, {downloading: false});
|
||||||
// runs initially, then runs every ${subscriptionCheckInterval} seconds
|
// runs initially, then runs every ${subscriptionCheckInterval} seconds
|
||||||
watchSubscriptions();
|
const watchSubscriptionsInterval = function() {
|
||||||
setInterval(() => {
|
|
||||||
watchSubscriptions();
|
watchSubscriptions();
|
||||||
}, subscriptionsCheckInterval * 1000);
|
const subscriptionsCheckInterval = config_api.getConfigItem('ytdl_subscriptions_check_interval');
|
||||||
|
setTimeout(watchSubscriptionsInterval, subscriptionsCheckInterval*1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
watchSubscriptionsInterval();
|
||||||
}
|
}
|
||||||
|
|
||||||
await db_api.importUnregisteredFiles();
|
await db_api.importUnregisteredFiles();
|
||||||
@@ -653,7 +670,6 @@ function loadConfigValues() {
|
|||||||
useDefaultDownloadingAgent = config_api.getConfigItem('ytdl_use_default_downloading_agent');
|
useDefaultDownloadingAgent = config_api.getConfigItem('ytdl_use_default_downloading_agent');
|
||||||
customDownloadingAgent = config_api.getConfigItem('ytdl_custom_downloading_agent');
|
customDownloadingAgent = config_api.getConfigItem('ytdl_custom_downloading_agent');
|
||||||
allowSubscriptions = config_api.getConfigItem('ytdl_allow_subscriptions');
|
allowSubscriptions = config_api.getConfigItem('ytdl_allow_subscriptions');
|
||||||
subscriptionsCheckInterval = config_api.getConfigItem('ytdl_subscriptions_check_interval');
|
|
||||||
|
|
||||||
if (!useDefaultDownloadingAgent && validDownloadingAgents.indexOf(customDownloadingAgent) !== -1 ) {
|
if (!useDefaultDownloadingAgent && validDownloadingAgents.indexOf(customDownloadingAgent) !== -1 ) {
|
||||||
logger.info(`Using non-default downloading agent \'${customDownloadingAgent}\'`)
|
logger.info(`Using non-default downloading agent \'${customDownloadingAgent}\'`)
|
||||||
@@ -678,6 +694,7 @@ function loadConfigValues() {
|
|||||||
|
|
||||||
function calculateSubcriptionRetrievalDelay(subscriptions_amount) {
|
function calculateSubcriptionRetrievalDelay(subscriptions_amount) {
|
||||||
// frequency is once every 5 mins by default
|
// frequency is once every 5 mins by default
|
||||||
|
const subscriptionsCheckInterval = config_api.getConfigItem('ytdl_subscriptions_check_interval');
|
||||||
let interval_in_ms = subscriptionsCheckInterval * 1000;
|
let interval_in_ms = subscriptionsCheckInterval * 1000;
|
||||||
const subinterval_in_ms = interval_in_ms/subscriptions_amount;
|
const subinterval_in_ms = interval_in_ms/subscriptions_amount;
|
||||||
return subinterval_in_ms;
|
return subinterval_in_ms;
|
||||||
@@ -722,6 +739,7 @@ async function watchSubscriptions() {
|
|||||||
}, current_delay);
|
}, current_delay);
|
||||||
subscription_timeouts[sub.id] = true;
|
subscription_timeouts[sub.id] = true;
|
||||||
current_delay += delay_interval;
|
current_delay += delay_interval;
|
||||||
|
const subscriptionsCheckInterval = config_api.getConfigItem('ytdl_subscriptions_check_interval');
|
||||||
if (current_delay >= subscriptionsCheckInterval * 1000) current_delay = 0;
|
if (current_delay >= subscriptionsCheckInterval * 1000) current_delay = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -751,20 +769,6 @@ function generateEnvVarConfigItem(key) {
|
|||||||
return {key: key, value: process['env'][key]};
|
return {key: key, value: process['env'][key]};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getThumbnailMp3(name)
|
|
||||||
{
|
|
||||||
var obj = utils.getJSONMp3(name, audioFolderPath);
|
|
||||||
var thumbnailLink = obj.thumbnail;
|
|
||||||
return thumbnailLink;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getThumbnailMp4(name)
|
|
||||||
{
|
|
||||||
var obj = utils.getJSONMp4(name, videoFolderPath);
|
|
||||||
var thumbnailLink = obj.thumbnail;
|
|
||||||
return thumbnailLink;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFileSizeMp3(name)
|
function getFileSizeMp3(name)
|
||||||
{
|
{
|
||||||
var jsonPath = audioFolderPath+name+".mp3.info.json";
|
var jsonPath = audioFolderPath+name+".mp3.info.json";
|
||||||
@@ -1058,7 +1062,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) {
|
|||||||
// create playlist
|
// create playlist
|
||||||
const playlist_name = file_objs.map(file_obj => file_obj.title).join(', ');
|
const playlist_name = file_objs.map(file_obj => file_obj.title).join(', ');
|
||||||
const duration = file_objs.reduce((a, b) => a + utils.durationStringToNumber(b.duration), 0);
|
const duration = file_objs.reduce((a, b) => a + utils.durationStringToNumber(b.duration), 0);
|
||||||
container = await db_api.createPlaylist(playlist_name, file_objs.map(file_obj => file_obj.uid), type, file_objs[0]['thumbnailURL'], options.user);
|
container = await db_api.createPlaylist(playlist_name, file_objs.map(file_obj => file_obj.uid), type, options.user);
|
||||||
} else if (file_objs.length === 1) {
|
} else if (file_objs.length === 1) {
|
||||||
container = file_objs[0];
|
container = file_objs[0];
|
||||||
} else {
|
} else {
|
||||||
@@ -1137,7 +1141,7 @@ async function generateArgs(url, type, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (useCookies) {
|
if (useCookies) {
|
||||||
if (await fs.pathExists(path.join(__dirname, 'appdata', 'cookies.txt'))) {
|
if (await fs.pathExists(path.join('appdata', 'cookies.txt'))) {
|
||||||
downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
|
downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
|
||||||
} else {
|
} 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.');
|
logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
|
||||||
@@ -1383,8 +1387,8 @@ async function autoUpdateYoutubeDL() {
|
|||||||
let stored_binary_path = current_app_details['path'];
|
let stored_binary_path = current_app_details['path'];
|
||||||
if (!stored_binary_path || typeof stored_binary_path !== 'string') {
|
if (!stored_binary_path || typeof stored_binary_path !== 'string') {
|
||||||
// logger.info(`INFO: Failed to get youtube-dl binary path at location: ${DETAILS_BIN_PATH}, attempting to guess actual path...`);
|
// logger.info(`INFO: Failed to get youtube-dl binary path at location: ${DETAILS_BIN_PATH}, attempting to guess actual path...`);
|
||||||
const guessed_base_path = 'node_modules/youtube-dl/bin/';
|
const guessed_base_path = YOUTUBE_DL_PATH;
|
||||||
const guessed_file_path = guessed_base_path + 'youtube-dl' + (is_windows ? '.exe' : '');
|
const guessed_file_path = path.join(guessed_base_path, 'youtube-dl' + (is_windows ? '.exe' : ''));
|
||||||
if (fs.existsSync(guessed_file_path)) {
|
if (fs.existsSync(guessed_file_path)) {
|
||||||
stored_binary_path = guessed_file_path;
|
stored_binary_path = guessed_file_path;
|
||||||
// logger.info('INFO: Guess successful! Update process continuing...')
|
// logger.info('INFO: Guess successful! Update process continuing...')
|
||||||
@@ -1435,7 +1439,7 @@ async function downloadLatestYoutubeDLBinary(new_version) {
|
|||||||
const file_ext = is_windows ? '.exe' : '';
|
const file_ext = is_windows ? '.exe' : '';
|
||||||
|
|
||||||
const download_url = `https://github.com/ytdl-org/youtube-dl/releases/latest/download/youtube-dl${file_ext}`;
|
const download_url = `https://github.com/ytdl-org/youtube-dl/releases/latest/download/youtube-dl${file_ext}`;
|
||||||
const output_path = `node_modules/youtube-dl/bin/youtube-dl${file_ext}`;
|
const output_path = path.join(YOUTUBE_DL_PATH, `youtube-dl${file_ext}`);
|
||||||
|
|
||||||
await fetchFile(download_url, output_path, `youtube-dl ${new_version}`);
|
await fetchFile(download_url, output_path, `youtube-dl ${new_version}`);
|
||||||
|
|
||||||
@@ -1446,7 +1450,7 @@ async function downloadLatestYoutubeDLCBinary(new_version) {
|
|||||||
const file_ext = is_windows ? '.exe' : '';
|
const file_ext = is_windows ? '.exe' : '';
|
||||||
|
|
||||||
const download_url = `https://github.com/blackjack4494/yt-dlc/releases/latest/download/youtube-dlc${file_ext}`;
|
const download_url = `https://github.com/blackjack4494/yt-dlc/releases/latest/download/youtube-dlc${file_ext}`;
|
||||||
const output_path = `node_modules/youtube-dl/bin/youtube-dl${file_ext}`;
|
const output_path = path.join(YOUTUBE_DL_PATH, `youtube-dl${file_ext}`);
|
||||||
|
|
||||||
await fetchFile(download_url, output_path, `youtube-dlc ${new_version}`);
|
await fetchFile(download_url, output_path, `youtube-dlc ${new_version}`);
|
||||||
|
|
||||||
@@ -1457,7 +1461,8 @@ async function downloadLatestYoutubeDLPBinary(new_version) {
|
|||||||
const file_ext = is_windows ? '.exe' : '';
|
const file_ext = is_windows ? '.exe' : '';
|
||||||
|
|
||||||
const download_url = `https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp${file_ext}`;
|
const download_url = `https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp${file_ext}`;
|
||||||
const output_path = `node_modules/youtube-dl/bin/youtube-dl${file_ext}`;
|
const output_path = path.join(YOUTUBE_DL_PATH, `youtube-dl${file_ext}`);
|
||||||
|
|
||||||
|
|
||||||
await fetchFile(download_url, output_path, `yt-dlp ${new_version}`);
|
await fetchFile(download_url, output_path, `yt-dlp ${new_version}`);
|
||||||
|
|
||||||
@@ -1611,9 +1616,10 @@ app.post('/api/transferDB', optionalJwt, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/testConnectionString', optionalJwt, async (req, res) => {
|
app.post('/api/testConnectionString', optionalJwt, async (req, res) => {
|
||||||
|
const connection_string = req.body.connection_string;
|
||||||
let success = null;
|
let success = null;
|
||||||
let error = '';
|
let error = '';
|
||||||
success = await db_api.connectToDB(0, true);
|
success = await db_api.connectToDB(0, true, connection_string);
|
||||||
if (!success) error = 'Connection string failed.';
|
if (!success) error = 'Connection string failed.';
|
||||||
|
|
||||||
res.send({success: success, error: error});
|
res.send({success: success, error: error});
|
||||||
@@ -2178,9 +2184,8 @@ app.post('/api/createPlaylist', optionalJwt, async (req, res) => {
|
|||||||
let playlistName = req.body.playlistName;
|
let playlistName = req.body.playlistName;
|
||||||
let uids = req.body.uids;
|
let uids = req.body.uids;
|
||||||
let type = req.body.type;
|
let type = req.body.type;
|
||||||
let thumbnailURL = req.body.thumbnailURL;
|
|
||||||
|
|
||||||
const new_playlist = await db_api.createPlaylist(playlistName, uids, type, thumbnailURL, req.isAuthenticated() ? req.user.uid : null);
|
const new_playlist = await db_api.createPlaylist(playlistName, uids, type, req.isAuthenticated() ? req.user.uid : null);
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
new_playlist: new_playlist,
|
new_playlist: new_playlist,
|
||||||
@@ -2213,8 +2218,18 @@ app.post('/api/getPlaylist', optionalJwt, async (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/api/getPlaylists', optionalJwt, async (req, res) => {
|
||||||
|
const uuid = req.isAuthenticated() ? req.user.uid : null;
|
||||||
|
|
||||||
|
const playlists = await db_api.getRecords('playlists', {user_uid: uuid});
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
playlists: playlists
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.post('/api/updatePlaylistFiles', optionalJwt, async (req, res) => {
|
app.post('/api/updatePlaylistFiles', optionalJwt, async (req, res) => {
|
||||||
let playlistID = req.body.playlistID;
|
let playlistID = req.body.playlist_id;
|
||||||
let uids = req.body.uids;
|
let uids = req.body.uids;
|
||||||
|
|
||||||
let success = false;
|
let success = false;
|
||||||
@@ -2235,6 +2250,20 @@ app.post('/api/updatePlaylistFiles', optionalJwt, async (req, res) => {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/api/addFileToPlaylist', optionalJwt, async (req, res) => {
|
||||||
|
let playlist_id = req.body.playlist_id;
|
||||||
|
let file_uid = req.body.file_uid;
|
||||||
|
|
||||||
|
const playlist = await db_api.getRecord('playlists', {id: playlist_id});
|
||||||
|
|
||||||
|
playlist.uids.push(file_uid);
|
||||||
|
|
||||||
|
let success = await db_api.updatePlaylist(playlist);
|
||||||
|
res.send({
|
||||||
|
success: success
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.post('/api/updatePlaylist', optionalJwt, async (req, res) => {
|
app.post('/api/updatePlaylist', optionalJwt, async (req, res) => {
|
||||||
let playlist = req.body.playlist;
|
let playlist = req.body.playlist;
|
||||||
let success = await db_api.updatePlaylist(playlist, req.user && req.user.uid);
|
let success = await db_api.updatePlaylist(playlist, req.user && req.user.uid);
|
||||||
@@ -2244,7 +2273,7 @@ app.post('/api/updatePlaylist', optionalJwt, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/deletePlaylist', optionalJwt, async (req, res) => {
|
app.post('/api/deletePlaylist', optionalJwt, async (req, res) => {
|
||||||
let playlistID = req.body.playlistID;
|
let playlistID = req.body.playlist_id;
|
||||||
|
|
||||||
let success = null;
|
let success = null;
|
||||||
try {
|
try {
|
||||||
@@ -2310,7 +2339,7 @@ app.post('/api/downloadFileFromServer', optionalJwt, async (req, res) => {
|
|||||||
const file_obj = await db_api.getVideo(uid, uuid, sub_id)
|
const file_obj = await db_api.getVideo(uid, uuid, sub_id)
|
||||||
file_path_to_download = file_obj.path;
|
file_path_to_download = file_obj.path;
|
||||||
}
|
}
|
||||||
if (!path.isAbsolute(file_path_to_download)) file_path_to_download = path.join(__dirname, file_path_to_download);
|
if (!path.isAbsolute(file_path_to_download)) file_path_to_download = path.join(file_path_to_download);
|
||||||
res.sendFile(file_path_to_download, function (err) {
|
res.sendFile(file_path_to_download, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
@@ -2339,9 +2368,9 @@ app.post('/api/downloadArchive', async (req, res) => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var upload_multer = multer({ dest: __dirname + '/appdata/' });
|
var upload_multer = multer({ dest: './appdata/' });
|
||||||
app.post('/api/uploadCookies', upload_multer.single('cookies'), async (req, res) => {
|
app.post('/api/uploadCookies', upload_multer.single('cookies'), async (req, res) => {
|
||||||
const new_path = path.join(__dirname, 'appdata', 'cookies.txt');
|
const new_path = path.join('appdata', 'cookies.txt');
|
||||||
|
|
||||||
if (await fs.pathExists(req.file.path)) {
|
if (await fs.pathExists(req.file.path)) {
|
||||||
await fs.rename(req.file.path, new_path);
|
await fs.rename(req.file.path, new_path);
|
||||||
@@ -2450,9 +2479,10 @@ app.get('/api/stream', optionalJwt, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/thumbnail/:path', optionalJwt, async (req, res) => {
|
app.get('/api/thumbnail/:uid', optionalJwt, async (req, res) => {
|
||||||
let file_path = decodeURIComponent(req.params.path);
|
const file_obj = await db_api.getRecord('files', {uid: req.params.uid});
|
||||||
if (fs.existsSync(file_path)) path.isAbsolute(file_path) ? res.sendFile(file_path) : res.sendFile(path.join(__dirname, file_path));
|
const file_path = file_obj['thumbnailPath'];
|
||||||
|
if (fs.existsSync(file_path)) res.sendFile(file_path, { root: '.' });
|
||||||
else res.sendStatus(404);
|
else res.sendStatus(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2623,7 +2653,7 @@ app.post('/api/deleteUser', optionalJwt, async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
let success = false;
|
let success = false;
|
||||||
let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
||||||
const user_folder = path.join(__dirname, usersFileFolder, uid);
|
const user_folder = path.join(usersFileFolder, uid);
|
||||||
const user_db_obj = await db_api.getRecord('users', {uid: uid});
|
const user_db_obj = await db_api.getRecord('users', {uid: uid});
|
||||||
if (user_db_obj) {
|
if (user_db_obj) {
|
||||||
// user exists, let's delete
|
// user exists, let's delete
|
||||||
@@ -2683,12 +2713,12 @@ app.use(function(req, res, next) {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
let index_path = path.join(__dirname, 'public', 'index.html');
|
let index_path = path.join('public', 'index.html');
|
||||||
|
|
||||||
fs.createReadStream(index_path).pipe(res);
|
fs.createReadStream(index_path).pipe(res);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let public_dir = path.join(__dirname, 'public');
|
let public_dir = path.join('public');
|
||||||
|
|
||||||
app.use(express.static(public_dir));
|
app.use(express.static(public_dir));
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const local_db_defaults = {}
|
|||||||
tables_list.forEach(table => {local_db_defaults[table] = []});
|
tables_list.forEach(table => {local_db_defaults[table] = []});
|
||||||
local_db.defaults(local_db_defaults).write();
|
local_db.defaults(local_db_defaults).write();
|
||||||
|
|
||||||
let using_local_db = config_api.getConfigItem('ytdl_use_local_db');
|
let using_local_db = null;
|
||||||
|
|
||||||
function setDB(input_db, input_users_db) {
|
function setDB(input_db, input_users_db) {
|
||||||
db = input_db; users_db = input_users_db;
|
db = input_db; users_db = input_users_db;
|
||||||
@@ -69,11 +69,14 @@ function setLogger(input_logger) {
|
|||||||
exports.initialize = (input_db, input_users_db, input_logger) => {
|
exports.initialize = (input_db, input_users_db, input_logger) => {
|
||||||
setDB(input_db, input_users_db);
|
setDB(input_db, input_users_db);
|
||||||
setLogger(input_logger);
|
setLogger(input_logger);
|
||||||
|
|
||||||
|
// must be done here to prevent getConfigItem from being called before init
|
||||||
|
using_local_db = config_api.getConfigItem('ytdl_use_local_db');
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.connectToDB = async (retries = 5, no_fallback = false) => {
|
exports.connectToDB = async (retries = 5, no_fallback = false, custom_connection_string = null) => {
|
||||||
if (using_local_db) return;
|
if (using_local_db) return;
|
||||||
const success = await exports._connectToDB();
|
const success = await exports._connectToDB(custom_connection_string);
|
||||||
if (success) return true;
|
if (success) return true;
|
||||||
|
|
||||||
if (retries) {
|
if (retries) {
|
||||||
@@ -105,8 +108,8 @@ exports.connectToDB = async (retries = 5, no_fallback = false) => {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports._connectToDB = async () => {
|
exports._connectToDB = async (custom_connection_string = null) => {
|
||||||
const uri = config_api.getConfigItem('ytdl_mongodb_connection_string'); // "mongodb://127.0.0.1:27017/?compressors=zlib&gssapiServiceName=mongodb";
|
const uri = !custom_connection_string ? config_api.getConfigItem('ytdl_mongodb_connection_string') : custom_connection_string; // "mongodb://127.0.0.1:27017/?compressors=zlib&gssapiServiceName=mongodb";
|
||||||
const client = new MongoClient(uri, {
|
const client = new MongoClient(uri, {
|
||||||
useNewUrlParser: true,
|
useNewUrlParser: true,
|
||||||
useUnifiedTopology: true,
|
useUnifiedTopology: true,
|
||||||
@@ -115,6 +118,10 @@ exports._connectToDB = async () => {
|
|||||||
try {
|
try {
|
||||||
await client.connect();
|
await client.connect();
|
||||||
database = client.db('ytdl_material');
|
database = client.db('ytdl_material');
|
||||||
|
|
||||||
|
// avoid doing anything else if it's just a test
|
||||||
|
if (custom_connection_string) return true;
|
||||||
|
|
||||||
const existing_collections = (await database.listCollections({}, { nameOnly: true }).toArray()).map(collection => collection.name);
|
const existing_collections = (await database.listCollections({}, { nameOnly: true }).toArray()).map(collection => collection.name);
|
||||||
|
|
||||||
const missing_tables = tables_list.filter(table => !(existing_collections.includes(table)));
|
const missing_tables = tables_list.filter(table => !(existing_collections.includes(table)));
|
||||||
@@ -227,7 +234,7 @@ function generateFileObject(id, type, customPath = null, sub = null) {
|
|||||||
const ext = (type === 'audio') ? '.mp3' : '.mp4'
|
const ext = (type === 'audio') ? '.mp3' : '.mp4'
|
||||||
const file_path = utils.getTrueFileName(jsonobj['_filename'], type); // path.join(type === 'audio' ? audioFolderPath : videoFolderPath, id + ext);
|
const file_path = utils.getTrueFileName(jsonobj['_filename'], type); // path.join(type === 'audio' ? audioFolderPath : videoFolderPath, id + ext);
|
||||||
// console.
|
// console.
|
||||||
var stats = fs.statSync(path.join(__dirname, file_path));
|
var stats = fs.statSync(path.join(file_path));
|
||||||
|
|
||||||
var title = jsonobj.title;
|
var title = jsonobj.title;
|
||||||
var url = jsonobj.webpage_url;
|
var url = jsonobj.webpage_url;
|
||||||
@@ -408,24 +415,27 @@ exports.addMetadataPropertyToDB = async (property_key) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.createPlaylist = async (playlist_name, uids, type, thumbnail_url, user_uid = null) => {
|
exports.createPlaylist = async (playlist_name, uids, type, user_uid = null) => {
|
||||||
|
const first_video = await exports.getVideo(uids[0]);
|
||||||
|
const thumbnailToUse = first_video['thumbnailURL'];
|
||||||
|
|
||||||
let new_playlist = {
|
let new_playlist = {
|
||||||
name: playlist_name,
|
name: playlist_name,
|
||||||
uids: uids,
|
uids: uids,
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
thumbnailURL: thumbnail_url,
|
thumbnailURL: thumbnailToUse,
|
||||||
type: type,
|
type: type,
|
||||||
registered: Date.now(),
|
registered: Date.now(),
|
||||||
randomize_order: false
|
randomize_order: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const duration = await exports.calculatePlaylistDuration(new_playlist, user_uid);
|
|
||||||
new_playlist.duration = duration;
|
|
||||||
|
|
||||||
new_playlist.user_uid = user_uid ? user_uid : undefined;
|
new_playlist.user_uid = user_uid ? user_uid : undefined;
|
||||||
|
|
||||||
await exports.insertRecordIntoTable('playlists', new_playlist);
|
await exports.insertRecordIntoTable('playlists', new_playlist);
|
||||||
|
|
||||||
|
const duration = await exports.calculatePlaylistDuration(new_playlist);
|
||||||
|
await exports.updateRecord('playlists', {id: new_playlist.id}, {duration: duration});
|
||||||
|
|
||||||
return new_playlist;
|
return new_playlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,10 +470,10 @@ exports.getPlaylist = async (playlist_id, user_uid = null, require_sharing = fal
|
|||||||
return playlist;
|
return playlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.updatePlaylist = async (playlist, user_uid = null) => {
|
exports.updatePlaylist = async (playlist) => {
|
||||||
let playlistID = playlist.id;
|
let playlistID = playlist.id;
|
||||||
|
|
||||||
const duration = await exports.calculatePlaylistDuration(playlist, user_uid);
|
const duration = await exports.calculatePlaylistDuration(playlist);
|
||||||
playlist.duration = duration;
|
playlist.duration = duration;
|
||||||
|
|
||||||
return await exports.updateRecord('playlists', {id: playlistID}, playlist);
|
return await exports.updateRecord('playlists', {id: playlistID}, playlist);
|
||||||
@@ -483,12 +493,12 @@ exports.setPlaylistProperty = async (playlist_id, assignment_obj, user_uid = nul
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.calculatePlaylistDuration = async (playlist, uuid, playlist_file_objs = null) => {
|
exports.calculatePlaylistDuration = async (playlist, playlist_file_objs = null) => {
|
||||||
if (!playlist_file_objs) {
|
if (!playlist_file_objs) {
|
||||||
playlist_file_objs = [];
|
playlist_file_objs = [];
|
||||||
for (let i = 0; i < playlist['uids'].length; i++) {
|
for (let i = 0; i < playlist['uids'].length; i++) {
|
||||||
const uid = playlist['uids'][i];
|
const uid = playlist['uids'][i];
|
||||||
const file_obj = await exports.getVideo(uid, uuid);
|
const file_obj = await exports.getVideo(uid);
|
||||||
if (file_obj) playlist_file_objs.push(file_obj);
|
if (file_obj) playlist_file_objs.push(file_obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -509,8 +519,8 @@ exports.deleteFile = async (uid, uuid = null, blacklistMode = false) => {
|
|||||||
var thumbnailPath = `${filePathNoExtension}.webp`;
|
var thumbnailPath = `${filePathNoExtension}.webp`;
|
||||||
var altThumbnailPath = `${filePathNoExtension}.jpg`;
|
var altThumbnailPath = `${filePathNoExtension}.jpg`;
|
||||||
|
|
||||||
jsonPath = path.join(__dirname, jsonPath);
|
jsonPath = path.join(jsonPath);
|
||||||
altJSONPath = path.join(__dirname, altJSONPath);
|
altJSONPath = path.join(altJSONPath);
|
||||||
|
|
||||||
let jsonExists = await fs.pathExists(jsonPath);
|
let jsonExists = await fs.pathExists(jsonPath);
|
||||||
let thumbnailExists = await fs.pathExists(thumbnailPath);
|
let thumbnailExists = await fs.pathExists(thumbnailPath);
|
||||||
@@ -585,7 +595,7 @@ exports.getVideoUIDByID = async (file_id, uuid = null) => {
|
|||||||
return file_obj ? file_obj['uid'] : null;
|
return file_obj ? file_obj['uid'] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getVideo = async (file_uid, uuid = null, sub_id = null) => {
|
exports.getVideo = async (file_uid) => {
|
||||||
return await exports.getRecord('files', {uid: file_uid});
|
return await exports.getRecord('files', {uid: file_uid});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const { app, BrowserWindow } = require('electron');
|
const { app, BrowserWindow } = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
|
const server = require('./app');
|
||||||
|
|
||||||
let win;
|
let win;
|
||||||
|
|
||||||
@@ -8,13 +9,7 @@ function createWindow() {
|
|||||||
win = new BrowserWindow({ width: 800, height: 600 });
|
win = new BrowserWindow({ width: 800, height: 600 });
|
||||||
|
|
||||||
// load the dist folder from Angular
|
// load the dist folder from Angular
|
||||||
win.loadURL(
|
win.loadURL('http://localhost:17442') //ADD THIS
|
||||||
url.format({
|
|
||||||
pathname: path.join(__dirname, `/dist/index.html`),
|
|
||||||
protocol: 'file:',
|
|
||||||
slashes: true
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// The following is optional and will open the DevTools:
|
// The following is optional and will open the DevTools:
|
||||||
// win.webContents.openDevTools()
|
// win.webContents.openDevTools()
|
||||||
1578
backend/package-lock.json
generated
1578
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,11 +2,14 @@
|
|||||||
"name": "backend",
|
"name": "backend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "backend for YoutubeDL-Material",
|
"description": "backend for YoutubeDL-Material",
|
||||||
"main": "index.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"start": "nodemon app.js",
|
"start": "nodemon app.js",
|
||||||
"debug": "set YTDL_MODE=debug && node app.js"
|
"debug": "set YTDL_MODE=debug && node app.js",
|
||||||
|
"electron": "electron main.js",
|
||||||
|
"pack": "electron-builder --dir",
|
||||||
|
"dist": "electron-builder"
|
||||||
},
|
},
|
||||||
"nodemonConfig": {
|
"nodemonConfig": {
|
||||||
"ignore": [
|
"ignore": [
|
||||||
@@ -19,6 +22,13 @@
|
|||||||
"restart_general.json"
|
"restart_general.json"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"build": {
|
||||||
|
"appId": "youtubedl.material",
|
||||||
|
"mac": {
|
||||||
|
"category": "public.app-category.utilities"
|
||||||
|
},
|
||||||
|
"files": ["!audio/*", "!video/*", "!users/*", "!subscriptions/*", "!appdata/*"]
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": ""
|
"url": ""
|
||||||
@@ -65,5 +75,9 @@
|
|||||||
"uuidv4": "^6.0.6",
|
"uuidv4": "^6.0.6",
|
||||||
"winston": "^3.2.1",
|
"winston": "^3.2.1",
|
||||||
"youtube-dl": "^3.0.2"
|
"youtube-dl": "^3.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"electron": "^13.1.7",
|
||||||
|
"electron-builder": "^22.11.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ async function getSubscriptionInfo(sub, user_uid = null) {
|
|||||||
let downloadConfig = ['--dump-json', '--playlist-end', '1'];
|
let downloadConfig = ['--dump-json', '--playlist-end', '1'];
|
||||||
let useCookies = config_api.getConfigItem('ytdl_use_cookies');
|
let useCookies = config_api.getConfigItem('ytdl_use_cookies');
|
||||||
if (useCookies) {
|
if (useCookies) {
|
||||||
if (await fs.pathExists(path.join(__dirname, 'appdata', 'cookies.txt'))) {
|
if (await fs.pathExists(path.join('appdata', 'cookies.txt'))) {
|
||||||
downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
|
downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
|
||||||
} else {
|
} 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.');
|
logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
|
||||||
@@ -117,7 +117,7 @@ async function getSubscriptionInfo(sub, user_uid = null) {
|
|||||||
const useArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
|
const useArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
|
||||||
if (useArchive && !sub.archive) {
|
if (useArchive && !sub.archive) {
|
||||||
// must create the archive
|
// must create the archive
|
||||||
const archive_dir = path.join(__dirname, basePath, 'archives', sub.name);
|
const archive_dir = path.join(basePath, 'archives', sub.name);
|
||||||
const archive_path = path.join(archive_dir, 'archive.txt');
|
const archive_path = path.join(archive_dir, 'archive.txt');
|
||||||
|
|
||||||
// creates archive directory and text file if it doesn't exist
|
// creates archive directory and text file if it doesn't exist
|
||||||
@@ -185,10 +185,10 @@ async function deleteSubscriptionFile(sub, file, deleteForever, file_uid = null,
|
|||||||
|
|
||||||
let filePath = appendedBasePath;
|
let filePath = appendedBasePath;
|
||||||
const ext = (sub.type && sub.type === 'audio') ? '.mp3' : '.mp4'
|
const ext = (sub.type && sub.type === 'audio') ? '.mp3' : '.mp4'
|
||||||
var jsonPath = path.join(__dirname,filePath,name+'.info.json');
|
var jsonPath = path.join(filePath,name+'.info.json');
|
||||||
var videoFilePath = path.join(__dirname,filePath,name+ext);
|
var videoFilePath = path.join(filePath,name+ext);
|
||||||
var imageFilePath = path.join(__dirname,filePath,name+'.jpg');
|
var imageFilePath = path.join(filePath,name+'.jpg');
|
||||||
var altImageFilePath = path.join(__dirname,filePath,name+'.webp');
|
var altImageFilePath = path.join(filePath,name+'.webp');
|
||||||
|
|
||||||
const [jsonExists, videoFileExists, imageFileExists, altImageFileExists] = await Promise.all([
|
const [jsonExists, videoFileExists, imageFileExists, altImageFileExists] = await Promise.all([
|
||||||
fs.pathExists(jsonPath),
|
fs.pathExists(jsonPath),
|
||||||
@@ -347,7 +347,9 @@ async function generateArgsForSubscription(sub, user_uid, redownload = false, de
|
|||||||
|
|
||||||
let appendedBasePath = getAppendedBasePath(sub, basePath);
|
let appendedBasePath = getAppendedBasePath(sub, basePath);
|
||||||
|
|
||||||
let fullOutput = `${appendedBasePath}/%(title)s.%(ext)s`;
|
const file_output = config_api.getConfigItem('ytdl_default_file_output') ? config_api.getConfigItem('ytdl_default_file_output') : '%(title)s';
|
||||||
|
|
||||||
|
let fullOutput = `${appendedBasePath}/${file_output}.%(ext)s`;
|
||||||
if (desired_path) {
|
if (desired_path) {
|
||||||
fullOutput = `${desired_path}.%(ext)s`;
|
fullOutput = `${desired_path}.%(ext)s`;
|
||||||
} else if (sub.custom_output) {
|
} else if (sub.custom_output) {
|
||||||
@@ -400,7 +402,7 @@ async function generateArgsForSubscription(sub, user_uid, redownload = false, de
|
|||||||
|
|
||||||
let useCookies = config_api.getConfigItem('ytdl_use_cookies');
|
let useCookies = config_api.getConfigItem('ytdl_use_cookies');
|
||||||
if (useCookies) {
|
if (useCookies) {
|
||||||
if (await fs.pathExists(path.join(__dirname, 'appdata', 'cookies.txt'))) {
|
if (await fs.pathExists(path.join('appdata', 'cookies.txt'))) {
|
||||||
downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
|
downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
|
||||||
} else {
|
} 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.');
|
logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
|
||||||
|
|||||||
@@ -24,10 +24,17 @@ export class CustomPlaylistsComponent implements OnInit {
|
|||||||
this.getAllPlaylists();
|
this.getAllPlaylists();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.postsService.playlists_changed.subscribe(changed => {
|
||||||
|
if (changed) {
|
||||||
|
this.getAllPlaylists();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllPlaylists() {
|
getAllPlaylists() {
|
||||||
this.playlists_received = false;
|
this.playlists_received = false;
|
||||||
|
// must call getAllFiles as we need to get category playlists as well
|
||||||
this.postsService.getAllFiles().subscribe(res => {
|
this.postsService.getAllFiles().subscribe(res => {
|
||||||
this.playlists = res['playlists'];
|
this.playlists = res['playlists'];
|
||||||
this.playlists_received = true;
|
this.playlists_received = true;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<ng-container *ngIf="normal_files_received && paged_data">
|
<ng-container *ngIf="normal_files_received && paged_data">
|
||||||
<div *ngFor="let file of paged_data; let i = index" class="mb-2 mt-2 d-flex justify-content-center" [ngClass]="[ postsService.card_size === 'small' ? 'col-2 small-col' : '', postsService.card_size === 'medium' ? 'col-6 col-lg-4 medium-col' : '', postsService.card_size === 'large' ? 'col-12 large-col' : '' ]">
|
<div *ngFor="let file of paged_data; let i = index" class="mb-2 mt-2 d-flex justify-content-center" [ngClass]="[ postsService.card_size === 'small' ? 'col-2 small-col' : '', postsService.card_size === 'medium' ? 'col-6 col-lg-4 medium-col' : '', postsService.card_size === 'large' ? 'col-12 large-col' : '' ]">
|
||||||
<app-unified-file-card [index]="i" [card_size]="postsService.card_size" [locale]="postsService.locale" (goToFile)="goToFile($event)" (goToSubscription)="goToSubscription($event)" [file_obj]="file" [use_youtubedl_archive]="postsService.config['Downloader']['use_youtubedl_archive']" [loading]="false" (deleteFile)="deleteFile($event)" [baseStreamPath]="postsService.path" [jwtString]="postsService.isLoggedIn ? '?jwt=' + this.postsService.token : ''"></app-unified-file-card>
|
<app-unified-file-card [index]="i" [card_size]="postsService.card_size" [locale]="postsService.locale" (goToFile)="goToFile($event)" (goToSubscription)="goToSubscription($event)" [file_obj]="file" [use_youtubedl_archive]="postsService.config['Downloader']['use_youtubedl_archive']" [availablePlaylists]="playlists" (addFileToPlaylist)="addFileToPlaylist($event)" [loading]="false" (deleteFile)="deleteFile($event)" [baseStreamPath]="postsService.path" [jwtString]="postsService.isLoggedIn ? '?jwt=' + this.postsService.token : ''"></app-unified-file-card>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="filtered_files.length === 0">
|
<div *ngIf="filtered_files.length === 0">
|
||||||
<ng-container i18n="No videos found">No videos found.</ng-container>
|
<ng-container i18n="No videos found">No videos found.</ng-container>
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ export class RecentVideosComponent implements OnInit {
|
|||||||
};
|
};
|
||||||
filterProperty = this.filterProperties['upload_date'];
|
filterProperty = this.filterProperties['upload_date'];
|
||||||
|
|
||||||
|
playlists = null;
|
||||||
|
|
||||||
pageSize = 10;
|
pageSize = 10;
|
||||||
paged_data = null;
|
paged_data = null;
|
||||||
|
|
||||||
@@ -68,14 +70,27 @@ export class RecentVideosComponent implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (this.postsService.initialized) {
|
if (this.postsService.initialized) {
|
||||||
this.getAllFiles();
|
this.getAllFiles();
|
||||||
|
this.getAllPlaylists();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.postsService.service_initialized.subscribe(init => {
|
this.postsService.service_initialized.subscribe(init => {
|
||||||
if (init) {
|
if (init) {
|
||||||
this.getAllFiles();
|
this.getAllFiles();
|
||||||
|
this.getAllPlaylists();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.postsService.files_changed.subscribe(changed => {
|
||||||
|
if (changed) {
|
||||||
|
this.getAllFiles();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.postsService.playlists_changed.subscribe(changed => {
|
||||||
|
if (changed) {
|
||||||
|
this.getAllPlaylists();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// set filter property to cached
|
// set filter property to cached
|
||||||
const cached_filter_property = localStorage.getItem('filter_property');
|
const cached_filter_property = localStorage.getItem('filter_property');
|
||||||
@@ -84,6 +99,12 @@ export class RecentVideosComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAllPlaylists() {
|
||||||
|
this.postsService.getPlaylists().subscribe(res => {
|
||||||
|
this.playlists = res['playlists'];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// search
|
// search
|
||||||
|
|
||||||
onSearchInputChanged(newvalue) {
|
onSearchInputChanged(newvalue) {
|
||||||
@@ -288,6 +309,23 @@ export class RecentVideosComponent implements OnInit {
|
|||||||
this.filterByProperty(this.filterProperty['property']);
|
this.filterByProperty(this.filterProperty['property']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addFileToPlaylist(info_obj) {
|
||||||
|
const file = info_obj['file'];
|
||||||
|
const playlist_id = info_obj['playlist_id'];
|
||||||
|
const playlist = this.playlists.find(potential_playlist => potential_playlist['id'] === playlist_id);
|
||||||
|
this.postsService.addFileToPlaylist(playlist_id, file['uid']).subscribe(res => {
|
||||||
|
if (res['success']) {
|
||||||
|
this.postsService.openSnackBar(`Successfully added ${file.title} to ${playlist.title}!`);
|
||||||
|
this.postsService.playlists_changed.next(true);
|
||||||
|
} else {
|
||||||
|
this.postsService.openSnackBar(`Failed to add ${file.title} to ${playlist.title}! Unknown error.`);
|
||||||
|
}
|
||||||
|
}, err => {
|
||||||
|
console.error(err);
|
||||||
|
this.postsService.openSnackBar(`Failed to add ${file.title} to ${playlist.title}! See browser console for error.`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// sorting and filtering
|
// sorting and filtering
|
||||||
|
|
||||||
sortFiles(a, b) {
|
sortFiles(a, b) {
|
||||||
|
|||||||
@@ -23,6 +23,12 @@
|
|||||||
<ng-container *ngIf="!is_playlist && !loading">
|
<ng-container *ngIf="!is_playlist && !loading">
|
||||||
<button (click)="openFileInfoDialog()" mat-menu-item><mat-icon>info</mat-icon><ng-container i18n="Video info button">Info</ng-container></button>
|
<button (click)="openFileInfoDialog()" mat-menu-item><mat-icon>info</mat-icon><ng-container i18n="Video info button">Info</ng-container></button>
|
||||||
<button (click)="navigateToSubscription()" mat-menu-item *ngIf="file_obj.sub_id"><mat-icon>{{file_obj.isAudio ? 'library_music' : 'video_library'}}</mat-icon> <ng-container i18n="Go to subscription menu item">Go to subscription</ng-container></button>
|
<button (click)="navigateToSubscription()" mat-menu-item *ngIf="file_obj.sub_id"><mat-icon>{{file_obj.isAudio ? 'library_music' : 'video_library'}}</mat-icon> <ng-container i18n="Go to subscription menu item">Go to subscription</ng-container></button>
|
||||||
|
<button *ngIf="availablePlaylists" [matMenuTriggerFor]="addtoplaylist" mat-menu-item><mat-icon>playlist_add</mat-icon> <ng-container i18n="Add to playlist menu item">Add to playlist</ng-container></button>
|
||||||
|
<mat-menu #addtoplaylist="matMenu">
|
||||||
|
<ng-container *ngFor="let playlist of availablePlaylists">
|
||||||
|
<button *ngIf="(playlist.type === 'audio') === file_obj.isAudio" [disabled]="playlist.uids?.includes(file_obj.uid)" (click)="emitAddFileToPlaylist(playlist.id)" mat-menu-item>{{playlist.name}}</button>
|
||||||
|
</ng-container>
|
||||||
|
</mat-menu>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<button *ngIf="file_obj.sub_id" (click)="emitDeleteFile()" mat-menu-item>
|
<button *ngIf="file_obj.sub_id" (click)="emitDeleteFile()" mat-menu-item>
|
||||||
<mat-icon>restore</mat-icon><ng-container i18n="Delete and redownload subscription video button">Delete and redownload</ng-container>
|
<mat-icon>restore</mat-icon><ng-container i18n="Delete and redownload subscription video button">Delete and redownload</ng-container>
|
||||||
|
|||||||
@@ -46,9 +46,11 @@ export class UnifiedFileCardComponent implements OnInit {
|
|||||||
@Input() locale = null;
|
@Input() locale = null;
|
||||||
@Input() baseStreamPath = null;
|
@Input() baseStreamPath = null;
|
||||||
@Input() jwtString = null;
|
@Input() jwtString = null;
|
||||||
|
@Input() availablePlaylists = null;
|
||||||
@Output() goToFile = new EventEmitter<any>();
|
@Output() goToFile = new EventEmitter<any>();
|
||||||
@Output() goToSubscription = new EventEmitter<any>();
|
@Output() goToSubscription = new EventEmitter<any>();
|
||||||
@Output() deleteFile = new EventEmitter<any>();
|
@Output() deleteFile = new EventEmitter<any>();
|
||||||
|
@Output() addFileToPlaylist = new EventEmitter<any>();
|
||||||
@Output() editPlaylist = new EventEmitter<any>();
|
@Output() editPlaylist = new EventEmitter<any>();
|
||||||
|
|
||||||
|
|
||||||
@@ -70,7 +72,7 @@ export class UnifiedFileCardComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.file_obj && this.file_obj.thumbnailPath) {
|
if (this.file_obj && this.file_obj.thumbnailPath) {
|
||||||
this.thumbnailBlobURL = `${this.baseStreamPath}thumbnail/${encodeURIComponent(this.file_obj.thumbnailPath)}${this.jwtString}`;
|
this.thumbnailBlobURL = `${this.baseStreamPath}thumbnail/${this.file_obj.uid}${this.jwtString}`;
|
||||||
/*const mime = getMimeByFilename(this.file_obj.thumbnailPath);
|
/*const mime = getMimeByFilename(this.file_obj.thumbnailPath);
|
||||||
const blob = new Blob([new Uint8Array(this.file_obj.thumbnailBlob.data)], {type: mime});
|
const blob = new Blob([new Uint8Array(this.file_obj.thumbnailBlob.data)], {type: mime});
|
||||||
const bloburl = URL.createObjectURL(blob);
|
const bloburl = URL.createObjectURL(blob);
|
||||||
@@ -86,6 +88,13 @@ export class UnifiedFileCardComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emitAddFileToPlaylist(playlist_id) {
|
||||||
|
this.addFileToPlaylist.emit({
|
||||||
|
file: this.file_obj,
|
||||||
|
playlist_id: playlist_id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
navigateToFile(event) {
|
navigateToFile(event) {
|
||||||
this.goToFile.emit({file: this.file_obj, event: event});
|
this.goToFile.emit({file: this.file_obj, event: event});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export class ModifyPlaylistComponent implements OnInit {
|
|||||||
this.playlist_updated = true;
|
this.playlist_updated = true;
|
||||||
this.postsService.openSnackBar('Playlist updated successfully.');
|
this.postsService.openSnackBar('Playlist updated successfully.');
|
||||||
this.getPlaylist();
|
this.getPlaylist();
|
||||||
|
this.postsService.playlists_changed.next(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +78,7 @@ export class ModifyPlaylistComponent implements OnInit {
|
|||||||
|
|
||||||
addContent(file) {
|
addContent(file) {
|
||||||
this.playlist_file_objs.push(file);
|
this.playlist_file_objs.push(file);
|
||||||
|
this.playlist.uids.push(file.uid);
|
||||||
this.processFiles();
|
this.processFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -951,8 +951,6 @@ export class MainComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reloadRecentVideos() {
|
reloadRecentVideos() {
|
||||||
if (this.recentVideos) {
|
this.postsService.files_changed.next(true);
|
||||||
this.recentVideos.getAllFiles();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ export class PostsService implements CanActivate {
|
|||||||
settings_changed = new BehaviorSubject<boolean>(false);
|
settings_changed = new BehaviorSubject<boolean>(false);
|
||||||
open_create_default_admin_dialog = new BehaviorSubject<boolean>(false);
|
open_create_default_admin_dialog = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
|
files_changed = new BehaviorSubject<boolean>(false);
|
||||||
|
playlists_changed = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
// app status
|
// app status
|
||||||
initialized = false;
|
initialized = false;
|
||||||
|
|
||||||
@@ -192,8 +195,8 @@ export class PostsService implements CanActivate {
|
|||||||
return this.http.post(this.path + 'transferDB', {local_to_remote: local_to_remote}, this.httpOptions);
|
return this.http.post(this.path + 'transferDB', {local_to_remote: local_to_remote}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
testConnectionString() {
|
testConnectionString(connection_string) {
|
||||||
return this.http.post(this.path + 'testConnectionString', {}, this.httpOptions);
|
return this.http.post(this.path + 'testConnectionString', {connection_string: connection_string}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
killAllDownloads() {
|
killAllDownloads() {
|
||||||
@@ -334,18 +337,28 @@ export class PostsService implements CanActivate {
|
|||||||
include_file_metadata: include_file_metadata}, this.httpOptions);
|
include_file_metadata: include_file_metadata}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPlaylists() {
|
||||||
|
return this.http.post(this.path + 'getPlaylists', {}, this.httpOptions);
|
||||||
|
}
|
||||||
|
|
||||||
updatePlaylist(playlist) {
|
updatePlaylist(playlist) {
|
||||||
return this.http.post(this.path + 'updatePlaylist', {playlist: playlist}, this.httpOptions);
|
return this.http.post(this.path + 'updatePlaylist', {playlist: playlist}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePlaylistFiles(playlistID, fileNames, type) {
|
updatePlaylistFiles(playlist_id, fileNames, type) {
|
||||||
return this.http.post(this.path + 'updatePlaylistFiles', {playlistID: playlistID,
|
return this.http.post(this.path + 'updatePlaylistFiles', {playlist_id: playlist_id,
|
||||||
fileNames: fileNames,
|
fileNames: fileNames,
|
||||||
type: type}, this.httpOptions);
|
type: type}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
removePlaylist(playlistID, type) {
|
addFileToPlaylist(playlist_id, file_uid) {
|
||||||
return this.http.post(this.path + 'deletePlaylist', {playlistID: playlistID, type: type}, this.httpOptions);
|
return this.http.post(this.path + 'addFileToPlaylist', {playlist_id: playlist_id,
|
||||||
|
file_uid: file_uid},
|
||||||
|
this.httpOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
removePlaylist(playlist_id, type) {
|
||||||
|
return this.http.post(this.path + 'deletePlaylist', {playlist_id: playlist_id, type: type}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// categories
|
// categories
|
||||||
|
|||||||
@@ -301,7 +301,7 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<div class="test-connection-div">
|
<div class="test-connection-div">
|
||||||
<button (click)="testConnectionString()" [disabled]="testing_connection_string" mat-flat-button color="accent"><ng-container i18n="Test connection string button">Test connection string</ng-container></button>
|
<button (click)="testConnectionString(new_config['Database']['mongodb_connection_string'])" [disabled]="testing_connection_string" mat-flat-button color="accent"><ng-container i18n="Test connection string button">Test connection string</ng-container></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="transfer-db-div">
|
<div class="transfer-db-div">
|
||||||
|
|||||||
@@ -307,9 +307,9 @@ export class SettingsComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
testConnectionString() {
|
testConnectionString(connection_string) {
|
||||||
this.testing_connection_string = true;
|
this.testing_connection_string = true;
|
||||||
this.postsService.testConnectionString().subscribe(res => {
|
this.postsService.testConnectionString(connection_string).subscribe(res => {
|
||||||
this.testing_connection_string = false;
|
this.testing_connection_string = false;
|
||||||
if (res['success']) {
|
if (res['success']) {
|
||||||
this.postsService.openSnackBar('Connection successful!');
|
this.postsService.openSnackBar('Connection successful!');
|
||||||
|
|||||||
Reference in New Issue
Block a user