Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into subscriptions-custom-path

pull/136/head
Tzahi12345 5 years ago
commit d3d0f92ea5

@ -1,18 +1,25 @@
FROM alpine:3.11 FROM alpine:3.12
RUN \ ENV UID=1000 GID=1000
apk add --no-cache npm python ffmpeg && \ RUN export user=youtube \
apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing/ \ && addgroup -S $user -g $GID && adduser -D -S $user -G $user -u $UID
USER $user
RUN apk add --no-cache \
ffmpeg \
npm \
python2 \
su-exec \
&& apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing/ \
atomicparsley atomicparsley
WORKDIR /app WORKDIR /app
COPY package.json /app/ COPY --chown=$UID:$GID [ "package.json", "package-lock.json", "/app/" ]
RUN npm install RUN npm install
COPY ./ /app/ COPY --chown=$UID:$GID [ "./", "/app/" ]
EXPOSE 17442 EXPOSE 17442
ENTRYPOINT [ "/app/entrypoint.sh" ]
CMD [ "node", "app.js" ] CMD [ "node", "app.js" ]

@ -8,6 +8,7 @@ var youtubedl = require('youtube-dl');
var ffmpeg = require('fluent-ffmpeg'); var ffmpeg = require('fluent-ffmpeg');
var compression = require('compression'); var compression = require('compression');
var https = require('https'); var https = require('https');
var multer = require('multer');
var express = require("express"); var express = require("express");
var bodyParser = require("body-parser"); var bodyParser = require("body-parser");
var archiver = require('archiver'); var archiver = require('archiver');
@ -164,7 +165,9 @@ var validDownloadingAgents = [
'ffmpeg', 'ffmpeg',
'httpie', 'httpie',
'wget' 'wget'
] ];
const subscription_timeouts = {};
// don't overwrite config if it already happened.. NOT // don't overwrite config if it already happened.. NOT
// let alreadyWritten = db.get('configWriteFlag').value(); // let alreadyWritten = db.get('configWriteFlag').value();
@ -615,15 +618,14 @@ function loadConfigValues() {
logger.transports[2].level = logger_level; logger.transports[2].level = logger_level;
} }
function calculateSubcriptionRetrievalDelay(amount) { function calculateSubcriptionRetrievalDelay(subscriptions_amount) {
// frequency is 5 mins // frequency is once every 5 mins by default
let frequency_in_ms = subscriptionsCheckInterval * 1000; let interval_in_ms = subscriptionsCheckInterval * 1000;
let minimum_frequency = 60 * 1000; const subinterval_in_ms = interval_in_ms/subscriptions_amount;
const first_frequency = frequency_in_ms/amount; return subinterval_in_ms;
return (first_frequency < minimum_frequency) ? minimum_frequency : first_frequency;
} }
function watchSubscriptions() { async function watchSubscriptions() {
let subscriptions = null; let subscriptions = null;
const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode'); const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode');
@ -645,10 +647,19 @@ function watchSubscriptions() {
let current_delay = 0; let current_delay = 0;
for (let i = 0; i < subscriptions.length; i++) { for (let i = 0; i < subscriptions.length; i++) {
let sub = subscriptions[i]; let sub = subscriptions[i];
// don't check the sub if the last check for the same subscription has not completed
if (subscription_timeouts[sub.id]) {
logger.verbose(`Subscription: skipped checking ${sub.name} as the last check for ${sub.name} has not completed.`);
continue;
}
logger.verbose('Watching ' + sub.name + ' with delay interval of ' + delay_interval); logger.verbose('Watching ' + sub.name + ' with delay interval of ' + delay_interval);
setTimeout(() => { setTimeout(async () => {
subscriptions_api.getVideosForSub(sub, sub.user_uid); await subscriptions_api.getVideosForSub(sub, sub.user_uid);
subscription_timeouts[sub.id] = false;
}, current_delay); }, current_delay);
subscription_timeouts[sub.id] = true;
current_delay += delay_interval; current_delay += delay_interval;
if (current_delay >= subscriptionsCheckInterval * 1000) current_delay = 0; if (current_delay >= subscriptionsCheckInterval * 1000) current_delay = 0;
} }
@ -1237,6 +1248,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) {
} else if (output) { } else if (output) {
if (output.length === 0 || output[0].length === 0) { if (output.length === 0 || output[0].length === 0) {
download['error'] = 'No output. Check if video already exists in your archive.'; download['error'] = 'No output. Check if video already exists in your archive.';
logger.warn(`No output received for video download, check if it exists in your archive.`)
updateDownloads(); updateDownloads();
resolve(false); resolve(false);
@ -1287,10 +1299,10 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) {
let is_playlist = file_names.length > 1; let is_playlist = file_names.length > 1;
if (options.merged_string) { if (options.merged_string !== null && options.merged_string !== undefined) {
let current_merged_archive = fs.readFileSync(fileFolderPath + 'merged.txt', 'utf8'); let current_merged_archive = fs.readFileSync(path.join(fileFolderPath, `merged_${type}.txt`), 'utf8');
let diff = current_merged_archive.replace(options.merged_string, ''); let diff = current_merged_archive.replace(options.merged_string, '');
const archive_path = path.join(archivePath, `archive_${type}.txt`); const archive_path = options.user ? path.join(fileFolderPath, 'archives', `archive_${type}.txt`) : path.join(archivePath, `archive_${type}.txt`);
fs.appendFileSync(archive_path, diff); fs.appendFileSync(archive_path, diff);
} }
@ -1318,7 +1330,7 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) {
const ext = is_audio ? '.mp3' : '.mp4'; const ext = is_audio ? '.mp3' : '.mp4';
var fileFolderPath = is_audio ? audioFolderPath : videoFolderPath; var fileFolderPath = is_audio ? audioFolderPath : videoFolderPath;
if (is_audio) options.skip_audio_args = true; if (is_audio && url.includes('youtu')) { options.skip_audio_args = true; }
// prepend with user if needed // prepend with user if needed
let multiUserMode = null; let multiUserMode = null;
@ -1421,10 +1433,10 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) {
const base_file_name = video_info._filename.substring(fileFolderPath.length, video_info._filename.length); const base_file_name = video_info._filename.substring(fileFolderPath.length, video_info._filename.length);
file_uid = registerFileDB(base_file_name, type, multiUserMode); file_uid = registerFileDB(base_file_name, type, multiUserMode);
if (options.merged_string) { if (options.merged_string !== null && options.merged_string !== undefined) {
let current_merged_archive = fs.readFileSync(fileFolderPath + 'merged.txt', 'utf8'); let current_merged_archive = fs.readFileSync(path.join(fileFolderPath, `merged_${type}.txt`), 'utf8');
let diff = current_merged_archive.replace(options.merged_string, ''); let diff = current_merged_archive.replace(options.merged_string, '');
const archive_path = req.isAuthenticated() ? path.join(fileFolderPath, 'archives', `archive_${type}.txt`) : path.join(archivePath, `archive_${type}.txt`); const archive_path = options.user ? path.join(fileFolderPath, 'archives', `archive_${type}.txt`) : path.join(archivePath, `archive_${type}.txt`);
fs.appendFileSync(archive_path, diff); fs.appendFileSync(archive_path, diff);
} }
@ -1453,6 +1465,7 @@ async function generateArgs(url, type, options) {
return new Promise(async resolve => { return new Promise(async resolve => {
var videopath = '%(title)s'; var videopath = '%(title)s';
var globalArgs = config_api.getConfigItem('ytdl_custom_args'); var globalArgs = config_api.getConfigItem('ytdl_custom_args');
let useCookies = config_api.getConfigItem('ytdl_use_cookies');
var is_audio = type === 'audio'; var is_audio = type === 'audio';
var fileFolderPath = is_audio ? audioFolderPath : videoFolderPath; var fileFolderPath = is_audio ? audioFolderPath : videoFolderPath;
@ -1474,10 +1487,12 @@ async function generateArgs(url, type, options) {
let downloadConfig = null; let downloadConfig = null;
let qualityPath = (is_audio && !options.skip_audio_args) ? '-f bestaudio' :'-f best[ext=mp4]'; let qualityPath = (is_audio && !options.skip_audio_args) ? '-f bestaudio' :'-f best[ext=mp4]';
const is_youtube = url.includes('youtu');
if (!is_audio && (url.includes('tiktok') || url.includes('pscp.tv'))) { if (!is_audio && !is_youtube) {
// tiktok videos fail when using the default format // tiktok videos fail when using the default format
qualityPath = '-f best'; qualityPath = null;
} else if (!is_audio && !is_youtube && (url.includes('reddit') || url.includes('pornhub'))) {
qualityPath = '-f bestvideo+bestaudio'
} }
if (customArgs) { if (customArgs) {
@ -1492,11 +1507,13 @@ async function generateArgs(url, type, options) {
} }
if (customOutput) { if (customOutput) {
downloadConfig = ['-o', path.join(fileFolderPath, customOutput) + ".%(ext)s", qualityPath, '--write-info-json', '--print-json']; downloadConfig = ['-o', path.join(fileFolderPath, customOutput) + ".%(ext)s", '--write-info-json', '--print-json'];
} else { } else {
downloadConfig = ['-o', path.join(fileFolderPath, videopath + (is_audio ? '.%(ext)s' : '.mp4')), qualityPath, '--write-info-json', '--print-json']; downloadConfig = ['-o', path.join(fileFolderPath, videopath + (is_audio ? '.%(ext)s' : '.mp4')), '--write-info-json', '--print-json'];
} }
if (qualityPath) downloadConfig.push(qualityPath);
if (is_audio && !options.skip_audio_args) { if (is_audio && !options.skip_audio_args) {
downloadConfig.push('-x'); downloadConfig.push('-x');
downloadConfig.push('--audio-format', 'mp3'); downloadConfig.push('--audio-format', 'mp3');
@ -1505,6 +1522,14 @@ async function generateArgs(url, type, options) {
if (youtubeUsername && youtubePassword) { if (youtubeUsername && youtubePassword) {
downloadConfig.push('--username', youtubeUsername, '--password', 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) { if (!useDefaultDownloadingAgent && customDownloadingAgent) {
downloadConfig.splice(0, 0, '--external-downloader', customDownloadingAgent); downloadConfig.splice(0, 0, '--external-downloader', customDownloadingAgent);
@ -1512,7 +1537,11 @@ async function generateArgs(url, type, options) {
let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive'); let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
if (useYoutubeDLArchive) { if (useYoutubeDLArchive) {
const archive_path = options.user ? path.join(fileFolderPath, 'archives', `archive_${type}.txt`) : path.join(archivePath, `archive_${type}.txt`); 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 // create archive file if it doesn't exist
if (!fs.existsSync(archive_path)) { if (!fs.existsSync(archive_path)) {
fs.closeSync(fs.openSync(archive_path, 'w')); fs.closeSync(fs.openSync(archive_path, 'w'));
@ -1524,7 +1553,7 @@ async function generateArgs(url, type, options) {
fs.closeSync(fs.openSync(blacklist_path, 'w')); fs.closeSync(fs.openSync(blacklist_path, 'w'));
} }
let merged_path = fileFolderPath + 'merged.txt'; let merged_path = path.join(fileFolderPath, `merged_${type}.txt`);
fs.ensureFileSync(merged_path); fs.ensureFileSync(merged_path);
// merges blacklist and regular archive // merges blacklist and regular archive
let inputPathList = [archive_path, blacklist_path]; let inputPathList = [archive_path, blacklist_path];
@ -1546,6 +1575,7 @@ async function generateArgs(url, type, options) {
} }
} }
logger.verbose(`youtube-dl args being used: ${downloadConfig.join(',')}`);
// downloadConfig.map((arg) => `"${arg}"`); // downloadConfig.map((arg) => `"${arg}"`);
resolve(downloadConfig); resolve(downloadConfig);
}); });
@ -1815,7 +1845,7 @@ const optionalJwt = function (req, res, next) {
res.sendStatus(401); res.sendStatus(401);
return; return;
} }
} else if (multiUserMode && !(req.path.includes('/api/auth/register') && !req.query.jwt)) { // registration should get passed through } else if (multiUserMode && !(req.path.includes('/api/auth/register') && !(req.path.includes('/api/config')) && !req.query.jwt)) { // registration should get passed through
if (!req.query.jwt) { if (!req.query.jwt) {
res.sendStatus(401); res.sendStatus(401);
return; return;
@ -1865,8 +1895,11 @@ app.post('/api/tomp3', optionalJwt, async function(req, res) {
user: req.isAuthenticated() ? req.user.uid : null user: req.isAuthenticated() ? req.user.uid : null
} }
const safeDownloadOverride = config_api.getConfigItem('ytdl_safe_download_override');
const is_playlist = url.includes('playlist'); const is_playlist = url.includes('playlist');
if (is_playlist || options.customQualityConfiguration || options.customArgs || options.maxBitrate)
let result_obj = null;
if (safeDownloadOverride || is_playlist || options.customQualityConfiguration || options.customArgs || options.maxBitrate)
result_obj = await downloadFileByURL_exec(url, 'audio', options, req.query.sessionID); result_obj = await downloadFileByURL_exec(url, 'audio', options, req.query.sessionID);
else else
result_obj = await downloadFileByURL_normal(url, 'audio', options, req.query.sessionID); result_obj = await downloadFileByURL_normal(url, 'audio', options, req.query.sessionID);
@ -1892,9 +1925,11 @@ app.post('/api/tomp4', optionalJwt, async function(req, res) {
user: req.isAuthenticated() ? req.user.uid : null user: req.isAuthenticated() ? req.user.uid : null
} }
const safeDownloadOverride = config_api.getConfigItem('ytdl_safe_download_override');
const is_playlist = url.includes('playlist'); const is_playlist = url.includes('playlist');
let result_obj = null; let result_obj = null;
if (is_playlist || options.customQualityConfiguration || options.customArgs || options.selectedHeight) if (safeDownloadOverride || is_playlist || options.customQualityConfiguration || options.customArgs || options.selectedHeight || !url.includes('youtu'))
result_obj = await downloadFileByURL_exec(url, 'video', options, req.query.sessionID); result_obj = await downloadFileByURL_exec(url, 'video', options, req.query.sessionID);
else else
result_obj = await downloadFileByURL_normal(url, 'video', options, req.query.sessionID); result_obj = await downloadFileByURL_normal(url, 'video', options, req.query.sessionID);
@ -2202,7 +2237,7 @@ app.post('/api/getSubscription', optionalJwt, async (req, res) => {
else else
base_path = config_api.getConfigItem('ytdl_subscriptions_base_path'); base_path = config_api.getConfigItem('ytdl_subscriptions_base_path');
let appended_base_path = path.join(base_path, subscription.isPlaylist ? 'playlists' : 'channels', subscription.name, '/'); let appended_base_path = path.join(base_path, (subscription.isPlaylist ? 'playlists' : 'channels'), subscription.name, '/');
let files; let files;
try { try {
files = recFindByExt(appended_base_path, 'mp4'); files = recFindByExt(appended_base_path, 'mp4');
@ -2532,6 +2567,25 @@ app.post('/api/downloadArchive', async (req, res) => {
}); });
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);
} else {
res.sendStatus(500);
return;
}
if (fs.existsSync(new_path)) {
res.send({success: true});
} else {
res.sendStatus(500);
}
});
// Updater API calls // Updater API calls
app.get('/api/updaterStatus', async (req, res) => { app.get('/api/updaterStatus', async (req, res) => {

@ -13,7 +13,8 @@
"path-audio": "audio/", "path-audio": "audio/",
"path-video": "video/", "path-video": "video/",
"use_youtubedl_archive": false, "use_youtubedl_archive": false,
"custom_args": "" "custom_args": "",
"safe_download_override": false
}, },
"Extra": { "Extra": {
"title_top": "YoutubeDL-Material", "title_top": "YoutubeDL-Material",
@ -49,6 +50,7 @@
"custom_downloading_agent": "", "custom_downloading_agent": "",
"multi_user_mode": false, "multi_user_mode": false,
"allow_advanced_download": false, "allow_advanced_download": false,
"use_cookies": false,
"logger_level": "info" "logger_level": "info"
} }
} }

@ -13,7 +13,8 @@
"path-audio": "audio/", "path-audio": "audio/",
"path-video": "video/", "path-video": "video/",
"use_youtubedl_archive": false, "use_youtubedl_archive": false,
"custom_args": "" "custom_args": "",
"safe_download_override": false
}, },
"Extra": { "Extra": {
"title_top": "YoutubeDL-Material", "title_top": "YoutubeDL-Material",
@ -49,6 +50,7 @@
"custom_downloading_agent": "", "custom_downloading_agent": "",
"multi_user_mode": false, "multi_user_mode": false,
"allow_advanced_download": false, "allow_advanced_download": false,
"use_cookies": false,
"logger_level": "info" "logger_level": "info"
} }
} }

@ -430,8 +430,8 @@ exports.deleteUserFile = function(user_uid, file_uid, type, blacklistMode = fals
fs.appendFileSync(blacklistPath, line); fs.appendFileSync(blacklistPath, line);
} }
} else { } else {
logger.info('Could not find archive file for audio files. Creating...'); logger.info(`Could not find archive file for ${type} files. Creating...`);
fs.closeSync(fs.openSync(archive_path, 'w')); fs.ensureFileSync(archive_path);
} }
} }
} }

@ -182,7 +182,8 @@ DEFAULT_CONFIG = {
"path-audio": "audio/", "path-audio": "audio/",
"path-video": "video/", "path-video": "video/",
"use_youtubedl_archive": false, "use_youtubedl_archive": false,
"custom_args": "" "custom_args": "",
"safe_download_override": false
}, },
"Extra": { "Extra": {
"title_top": "YoutubeDL-Material", "title_top": "YoutubeDL-Material",
@ -218,6 +219,7 @@ DEFAULT_CONFIG = {
"custom_downloading_agent": "", "custom_downloading_agent": "",
"multi_user_mode": false, "multi_user_mode": false,
"allow_advanced_download": false, "allow_advanced_download": false,
"use_cookies": false,
"logger_level": "info" "logger_level": "info"
} }
} }

@ -40,6 +40,10 @@ let CONFIG_ITEMS = {
'key': 'ytdl_custom_args', 'key': 'ytdl_custom_args',
'path': 'YoutubeDLMaterial.Downloader.custom_args' 'path': 'YoutubeDLMaterial.Downloader.custom_args'
}, },
'ytdl_safe_download_override': {
'key': 'ytdl_safe_download_override',
'path': 'YoutubeDLMaterial.Downloader.safe_download_override'
},
// Extra // Extra
'ytdl_title_top': { 'ytdl_title_top': {
@ -148,6 +152,10 @@ let CONFIG_ITEMS = {
'key': 'ytdl_allow_advanced_download', 'key': 'ytdl_allow_advanced_download',
'path': 'YoutubeDLMaterial.Advanced.allow_advanced_download' 'path': 'YoutubeDLMaterial.Advanced.allow_advanced_download'
}, },
'ytdl_use_cookies': {
'key': 'ytdl_use_cookies',
'path': 'YoutubeDLMaterial.Advanced.use_cookies'
},
'ytdl_logger_level': { 'ytdl_logger_level': {
'key': 'ytdl_logger_level', 'key': 'ytdl_logger_level',
'path': 'YoutubeDLMaterial.Advanced.logger_level' 'path': 'YoutubeDLMaterial.Advanced.logger_level'

@ -0,0 +1,17 @@
#!/bin/sh
set -eu
CMD="node app.js"
# if the first arg starts with "-" pass it to program
if [ "${1#-}" != "$1" ]; then
set -- "$CMD" "$@"
fi
# chown current working directory to current user
if [ "$@" = "$CMD" ] && [ "$(id -u)" = "0" ]; then
find . \! -user "$UID" -exec chown "$UID:$GID" -R '{}' +
exec su-exec "$UID:$GID" "$0" "$@"
fi
exec "$@"

@ -59,6 +59,11 @@
"picomatch": "^2.0.4" "picomatch": "^2.0.4"
} }
}, },
"append-field": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
"integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY="
},
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
@ -324,6 +329,11 @@
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
}, },
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
},
"buffer-indexof-polyfill": { "buffer-indexof-polyfill": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.1.tgz", "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.1.tgz",
@ -334,6 +344,38 @@
"resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
"integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=" "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s="
}, },
"busboy": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
"integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=",
"requires": {
"dicer": "0.2.5",
"readable-stream": "1.1.x"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
}
}
},
"bytes": { "bytes": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
@ -524,6 +566,33 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
}, },
"concat-stream": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
"requires": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^2.2.2",
"typedarray": "^0.0.6"
},
"dependencies": {
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
}
}
},
"config": { "config": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/config/-/config-3.3.1.tgz", "resolved": "https://registry.npmjs.org/config/-/config-3.3.1.tgz",
@ -679,6 +748,38 @@
"kuler": "1.0.x" "kuler": "1.0.x"
} }
}, },
"dicer": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
"integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=",
"requires": {
"readable-stream": "1.1.x",
"streamsearch": "0.1.2"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
}
}
},
"dot-prop": { "dot-prop": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
@ -1718,6 +1819,21 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}, },
"multer": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.2.tgz",
"integrity": "sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==",
"requires": {
"append-field": "^1.0.0",
"busboy": "^0.2.11",
"concat-stream": "^1.5.2",
"mkdirp": "^0.5.1",
"object-assign": "^4.1.1",
"on-finished": "^2.3.0",
"type-is": "^1.6.4",
"xtend": "^4.0.0"
}
},
"multistream": { "multistream": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/multistream/-/multistream-2.1.1.tgz", "resolved": "https://registry.npmjs.org/multistream/-/multistream-2.1.1.tgz",
@ -2434,6 +2550,11 @@
"hashish": "~0.0.4" "hashish": "~0.0.4"
} }
}, },
"streamsearch": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
},
"string-width": { "string-width": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
@ -2670,6 +2791,11 @@
"mime-types": "~2.1.24" "mime-types": "~2.1.24"
} }
}, },
"typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
},
"undefsafe": { "undefsafe": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz",
@ -2905,6 +3031,11 @@
"resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz",
"integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ="
}, },
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
"yallist": { "yallist": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",

@ -41,6 +41,7 @@
"lowdb": "^1.0.0", "lowdb": "^1.0.0",
"md5": "^2.2.1", "md5": "^2.2.1",
"merge-files": "^0.1.2", "merge-files": "^0.1.2",
"multer": "^1.4.2",
"node-fetch": "^2.6.0", "node-fetch": "^2.6.0",
"node-id3": "^0.1.14", "node-id3": "^0.1.14",
"nodemon": "^2.0.2", "nodemon": "^2.0.2",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -215,6 +215,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
ngx-file-drop
MIT
ngx-videogular ngx-videogular
MIT MIT

@ -14,5 +14,5 @@
<link rel="stylesheet" href="styles.5112d6db78cf21541598.css"></head> <link rel="stylesheet" href="styles.5112d6db78cf21541598.css"></head>
<body> <body>
<app-root></app-root> <app-root></app-root>
<script src="runtime-es2015.adce6980ecb528c465bb.js" type="module"></script><script src="runtime-es5.adce6980ecb528c465bb.js" nomodule defer></script><script src="polyfills-es5.7f923c8f5afda210edd3.js" nomodule defer></script><script src="polyfills-es2015.5b408f108bcea938a7e2.js" type="module"></script><script src="main-es2015.38c23f6efbbfa0797d6d.js" type="module"></script><script src="main-es5.38c23f6efbbfa0797d6d.js" nomodule defer></script></body> <script src="runtime-es2015.6ca29b87fe1fc2ebfd61.js" type="module"></script><script src="runtime-es5.6ca29b87fe1fc2ebfd61.js" nomodule defer></script><script src="polyfills-es5.7f923c8f5afda210edd3.js" nomodule defer></script><script src="polyfills-es2015.5b408f108bcea938a7e2.js" type="module"></script><script src="main-es2015.0cbc545a4a3bee376826.js" type="module"></script><script src="main-es5.0cbc545a4a3bee376826.js" nomodule defer></script></body>
</html> </html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
!function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],p=0,s=[];p<a.length;p++)i=a[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++)0!==o[t[a]]&&(n=!1);n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={0:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+""+({}[e]||e)+"-es2015."+{1:"d61dc0b722bb5a6d7e07"}[e]+".js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,(function(r){return e[r]}).bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="",i.oe=function(e){throw console.error(e),e};var a=window.webpackJsonp=window.webpackJsonp||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([]); !function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],p=0,s=[];p<a.length;p++)i=a[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++)0!==o[t[a]]&&(n=!1);n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={0:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+""+({}[e]||e)+"-es2015."+{1:"19816864d8bf40ef8134"}[e]+".js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,(function(r){return e[r]}).bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="",i.oe=function(e){throw console.error(e),e};var a=window.webpackJsonp=window.webpackJsonp||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([]);

@ -1 +1 @@
!function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],p=0,s=[];p<a.length;p++)i=a[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++)0!==o[t[a]]&&(n=!1);n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={0:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+""+({}[e]||e)+"-es5."+{1:"d61dc0b722bb5a6d7e07"}[e]+".js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,(function(r){return e[r]}).bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="",i.oe=function(e){throw console.error(e),e};var a=window.webpackJsonp=window.webpackJsonp||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([]); !function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],p=0,s=[];p<a.length;p++)i=a[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++)0!==o[t[a]]&&(n=!1);n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={0:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+""+({}[e]||e)+"-es5."+{1:"19816864d8bf40ef8134"}[e]+".js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,(function(r){return e[r]}).bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="",i.oe=function(e){throw console.error(e),e};var a=window.webpackJsonp=window.webpackJsonp||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([]);

@ -240,10 +240,10 @@ async function getVideosForSub(sub, user_uid = null) {
if (sub.name) { if (sub.name) {
appendedBasePath = getAppendedBasePath(sub, basePath); appendedBasePath = getAppendedBasePath(sub, basePath);
} else { } else {
appendedBasePath = basePath + (sub.isPlaylist ? 'playlists/%(playlist_title)s' : 'channels/%(uploader)s'); appendedBasePath = path.join(basePath, (sub.isPlaylist ? 'playlists/%(playlist_title)s' : 'channels/%(uploader)s'));
} }
let downloadConfig = ['-o', appendedBasePath + '/%(title)s.mp4', '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4', '-ciw', '--write-annotations', '--write-thumbnail', '--write-info-json', '--print-json']; let downloadConfig = ['-o', appendedBasePath + '/%(title)s.mp4', '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4', '-ciw', '--write-info-json', '--print-json'];
let archive_dir = null; let archive_dir = null;
let archive_path = null; let archive_path = null;
@ -265,11 +265,38 @@ async function getVideosForSub(sub, user_uid = null) {
downloadConfig.push('--dateafter', 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.');
}
}
// get videos // get videos
logger.verbose('Subscribe: getting videos for subscription ' + sub.name); logger.verbose('Subscription: getting videos for subscription ' + sub.name);
youtubedl.exec(sub.url, downloadConfig, {}, function(err, output) { youtubedl.exec(sub.url, downloadConfig, {}, function(err, output) {
if (err) { logger.verbose('Subscription: finished check for ' + sub.name);
if (err && !output) {
logger.error(err.stderr); logger.error(err.stderr);
if (err.stderr.includes('This video is unavailable')) {
logger.info('An error was encountered with at least one video, backup method will be used.')
try {
const outputs = err.stdout.split(/\r\n|\r|\n/);
for (let i = 0; i < outputs.length; i++) {
const output = JSON.parse(outputs[i]);
handleOutputJSON(sub, sub_db, output, i === 0)
if (err.stderr.includes(output['id']) && archive_path) {
// we found a video that errored! add it to the archive to prevent future errors
fs.appendFileSync(archive_path, output['id']);
}
}
} catch(e) {
logger.error('Backup method failed. See error below:');
logger.error(e);
}
}
resolve(false); resolve(false);
} else if (output) { } else if (output) {
if (output.length === 0 || (output.length === 1 && output[0] === '')) { if (output.length === 0 || (output.length === 1 && output[0] === '')) {
@ -287,17 +314,8 @@ async function getVideosForSub(sub, user_uid = null) {
continue; continue;
} }
if (sub.streamingOnly) { const reset_videos = i === 0;
if (i === 0) { handleOutputJSON(sub, sub_db, output_json, reset_videos);
sub_db.assign({videos: []}).write();
}
// remove unnecessary info
output_json.formats = null;
// add to db
sub_db.get('videos').push(output_json).write();
}
// TODO: Potentially store downloaded files in db? // TODO: Potentially store downloaded files in db?
@ -308,6 +326,20 @@ async function getVideosForSub(sub, user_uid = null) {
}); });
} }
function handleOutputJSON(sub, sub_db, output_json, reset_videos = false) {
if (sub.streamingOnly) {
if (reset_videos) {
sub_db.assign({videos: []}).write();
}
// remove unnecessary info
output_json.formats = null;
// add to db
sub_db.get('videos').push(output_json).write();
}
}
function getAllSubscriptions(user_uid = null) { function getAllSubscriptions(user_uid = null) {
if (user_uid) if (user_uid)
return users_db.get('users').find({uid: user_uid}).get('subscriptions').value(); return users_db.get('users').find({uid: user_uid}).get('subscriptions').value();

7
package-lock.json generated

@ -1,6 +1,6 @@
{ {
"name": "youtube-dl-material", "name": "youtube-dl-material",
"version": "3.6.0", "version": "4.0.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -9113,6 +9113,11 @@
"resolved": "https://registry.npmjs.org/ng-lazyload-image/-/ng-lazyload-image-7.1.0.tgz", "resolved": "https://registry.npmjs.org/ng-lazyload-image/-/ng-lazyload-image-7.1.0.tgz",
"integrity": "sha512-1fip2FdPBDRnjGyBokI/DupBxOnrKh2lbtT8X8N1oPbE3KBZXXl82VIKcK2Sx+XQD67/+VtFzlISmrgsatzYuw==" "integrity": "sha512-1fip2FdPBDRnjGyBokI/DupBxOnrKh2lbtT8X8N1oPbE3KBZXXl82VIKcK2Sx+XQD67/+VtFzlISmrgsatzYuw=="
}, },
"ngx-file-drop": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/ngx-file-drop/-/ngx-file-drop-9.0.1.tgz",
"integrity": "sha512-xtUUjGMr9c8wwSfA4Cyy0iZMPLnBOg9i32A3tHOPfEivRrn9evULvxriCM45Qz6HpuuqA7vZGxGZZTCUIj/h3A=="
},
"ngx-videogular": { "ngx-videogular": {
"version": "9.0.1", "version": "9.0.1",
"resolved": "https://registry.npmjs.org/ngx-videogular/-/ngx-videogular-9.0.1.tgz", "resolved": "https://registry.npmjs.org/ngx-videogular/-/ngx-videogular-9.0.1.tgz",

@ -33,9 +33,10 @@
"core-js": "^2.4.1", "core-js": "^2.4.1",
"file-saver": "^2.0.2", "file-saver": "^2.0.2",
"filesize": "^6.1.0", "filesize": "^6.1.0",
"fingerprintjs2": "^2.1.0",
"ng-lazyload-image": "^7.0.1", "ng-lazyload-image": "^7.0.1",
"ngx-file-drop": "^9.0.1",
"ngx-videogular": "^9.0.1", "ngx-videogular": "^9.0.1",
"fingerprintjs2": "^2.1.0",
"rxjs": "^6.5.3", "rxjs": "^6.5.3",
"rxjs-compat": "^6.0.0-rc.0", "rxjs-compat": "^6.0.0-rc.0",
"tslib": "^1.10.0", "tslib": "^1.10.0",

@ -40,9 +40,10 @@
<mat-sidenav-container style="height: 100%"> <mat-sidenav-container style="height: 100%">
<mat-sidenav #sidenav> <mat-sidenav #sidenav>
<mat-nav-list> <mat-nav-list>
<a mat-list-item (click)="sidenav.close()" routerLink='/home'><ng-container i18n="Navigation menu Home Page title">Home</ng-container></a> <a *ngIf="postsService.config && (!postsService.config.Advanced.multi_user_mode || postsService.isLoggedIn)" mat-list-item (click)="sidenav.close()" routerLink='/home'><ng-container i18n="Navigation menu Home Page title">Home</ng-container></a>
<a *ngIf="allowSubscriptions && (!postsService.isLoggedIn || postsService.permissions.includes('subscriptions'))" mat-list-item (click)="sidenav.close()" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a> <a *ngIf="postsService.config && postsService.config.Advanced.multi_user_mode && !postsService.isLoggedIn" mat-list-item (click)="sidenav.close()" routerLink='/login'><ng-container i18n="Navigation menu Login Page title">Login</ng-container></a>
<a *ngIf="enableDownloadsManager && (!postsService.isLoggedIn || postsService.permissions.includes('downloads_manager'))" mat-list-item (click)="sidenav.close()" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</ng-container></a> <a *ngIf="postsService.config && allowSubscriptions && (!postsService.config.Advanced.multi_user_mode || (postsService.isLoggedIn && postsService.permissions.includes('subscriptions')))" mat-list-item (click)="sidenav.close()" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a>
<a *ngIf="postsService.config && enableDownloadsManager && (!postsService.config.Advanced.multi_user_mode || (postsService.isLoggedIn && postsService.permissions.includes('downloads_manager')))" mat-list-item (click)="sidenav.close()" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</ng-container></a>
</mat-nav-list> </mat-nav-list>
</mat-sidenav> </mat-sidenav>
<mat-sidenav-content [style.background]="postsService.theme ? postsService.theme.background_color : null"> <mat-sidenav-content [style.background]="postsService.theme ? postsService.theme.background_color : null">

@ -1,5 +1,5 @@
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule, LOCALE_ID } from '@angular/core'; import { NgModule, LOCALE_ID } from '@angular/core';
import { registerLocaleData, CommonModule } from '@angular/common'; import { registerLocaleData, CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
@ -25,21 +25,21 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatToolbarModule } from '@angular/material/toolbar'; import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTabsModule } from '@angular/material/tabs'; import { MatTabsModule } from '@angular/material/tabs';
import {MatPaginatorModule} from '@angular/material/paginator'; import { MatPaginatorModule } from '@angular/material/paginator';
import {MatSortModule} from '@angular/material/sort'; import { MatSortModule } from '@angular/material/sort';
import {MatTableModule} from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import {DragDropModule} from '@angular/cdk/drag-drop'; import { DragDropModule } from '@angular/cdk/drag-drop';
import {ClipboardModule} from '@angular/cdk/clipboard'; import { ClipboardModule } from '@angular/cdk/clipboard';
import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { HttpClientModule, HttpClient } from '@angular/common/http'; import { HttpClientModule, HttpClient } from '@angular/common/http';
import { PostsService } from 'app/posts.services'; import { PostsService } from 'app/posts.services';
import { FileCardComponent } from './file-card/file-card.component'; import { FileCardComponent } from './file-card/file-card.component';
import {RouterModule} from '@angular/router'; import { RouterModule } from '@angular/router';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { MainComponent } from './main/main.component'; import { MainComponent } from './main/main.component';
import { PlayerComponent } from './player/player.component'; import { PlayerComponent } from './player/player.component';
import {VgCoreModule, VgControlsModule, VgOverlayPlayModule, VgBufferingModule} from 'ngx-videogular'; import { VgCoreModule, VgControlsModule, VgOverlayPlayModule, VgBufferingModule } from 'ngx-videogular';
import { InputDialogComponent } from './input-dialog/input-dialog.component'; import { InputDialogComponent } from './input-dialog/input-dialog.component';
import { LazyLoadImageModule, IsVisibleProps } from 'ng-lazyload-image'; import { LazyLoadImageModule, IsVisibleProps } from 'ng-lazyload-image';
import { audioFilesMouseHovering, videoFilesMouseHovering, audioFilesOpened, videoFilesOpened } from './main/main.component'; import { audioFilesMouseHovering, videoFilesMouseHovering, audioFilesOpened, videoFilesOpened } from './main/main.component';
@ -52,7 +52,8 @@ import { SubscriptionFileCardComponent } from './subscription/subscription-file-
import { SubscriptionInfoDialogComponent } from './dialogs/subscription-info-dialog/subscription-info-dialog.component'; import { SubscriptionInfoDialogComponent } from './dialogs/subscription-info-dialog/subscription-info-dialog.component';
import { SettingsComponent } from './settings/settings.component'; import { SettingsComponent } from './settings/settings.component';
import { CheckOrSetPinDialogComponent } from './dialogs/check-or-set-pin-dialog/check-or-set-pin-dialog.component'; import { CheckOrSetPinDialogComponent } from './dialogs/check-or-set-pin-dialog/check-or-set-pin-dialog.component';
import {MatChipsModule} from '@angular/material/chips'; import { MatChipsModule } from '@angular/material/chips';
import { NgxFileDropModule } from 'ngx-file-drop';
import es from '@angular/common/locales/es'; import es from '@angular/common/locales/es';
import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.component'; import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.component';
@ -69,6 +70,8 @@ import { ModifyUsersComponent } from './components/modify-users/modify-users.com
import { AddUserDialogComponent } from './dialogs/add-user-dialog/add-user-dialog.component'; import { AddUserDialogComponent } from './dialogs/add-user-dialog/add-user-dialog.component';
import { ManageUserComponent } from './components/manage-user/manage-user.component'; import { ManageUserComponent } from './components/manage-user/manage-user.component';
import { ManageRoleComponent } from './components/manage-role/manage-role.component'; import { ManageRoleComponent } from './components/manage-role/manage-role.component';
import { CookiesUploaderDialogComponent } from './dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component';
registerLocaleData(es, 'es'); registerLocaleData(es, 'es');
export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps<any>) { export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps<any>) {
@ -105,7 +108,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
ModifyUsersComponent, ModifyUsersComponent,
AddUserDialogComponent, AddUserDialogComponent,
ManageUserComponent, ManageUserComponent,
ManageRoleComponent ManageRoleComponent,
CookiesUploaderDialogComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -145,6 +149,7 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
MatChipsModule, MatChipsModule,
DragDropModule, DragDropModule,
ClipboardModule, ClipboardModule,
NgxFileDropModule,
VgCoreModule, VgCoreModule,
VgControlsModule, VgControlsModule,
VgOverlayPlayModule, VgOverlayPlayModule,

@ -0,0 +1,40 @@
<h4 mat-dialog-title i18n="Cookies uploader dialog title">Upload new cookies</h4>
<mat-dialog-content>
<div>
<div class="center">
<ngx-file-drop [multiple]="false" accept=".txt" dropZoneLabel="Drop files here" (onFileDrop)="dropped($event)"
(onFileOver)="fileOver($event)" (onFileLeave)="fileLeave($event)">
<ng-template ngx-file-drop-content-tmp let-openFileSelector="openFileSelector">
<div>
<div>
<ng-container i18n="Drag and Drop">Drag and Drop</ng-container>
</div>
<div style="margin-top: 6px;">
<button mat-stroked-button (click)="openFileSelector()">Browse Files</button>
</div>
</div>
</ng-template>
</ngx-file-drop>
<div style="margin-top: 15px;">
<p style="font-size: 14px;" i18n="Cookies upload warning">NOTE: Uploading new cookies will overrride your previous cookies. Also note that cookies are instance-wide, not per-user.</p>
</div>
<div style="margin-top: 10px;">
<table class="table">
<tbody class="upload-name-style">
<tr *ngFor="let item of files; let i=index">
<td style="vertical-align: middle;">
<strong>{{ item.relativePath }}</strong>
</td>
<td>
<button [disabled]="uploading || uploaded" (click)="uploadFile()" style="float: right" matTooltip="Upload" mat-mini-fab><mat-icon>publish</mat-icon><mat-spinner *ngIf="uploading" class="spinner" [diameter]="38"></mat-spinner></button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions><button style="margin-bottom: 5px;" mat-dialog-close mat-stroked-button><ng-container i18n="Close">Close</ng-container></button></mat-dialog-actions>

@ -0,0 +1,5 @@
.spinner {
bottom: 1px;
left: 0.5px;
position: absolute;
}

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CookiesUploaderDialogComponent } from './cookies-uploader-dialog.component';
describe('CookiesUploaderDialogComponent', () => {
let component: CookiesUploaderDialogComponent;
let fixture: ComponentFixture<CookiesUploaderDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CookiesUploaderDialogComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CookiesUploaderDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,57 @@
import { Component, OnInit } from '@angular/core';
import { NgxFileDropEntry, FileSystemFileEntry, FileSystemDirectoryEntry } from 'ngx-file-drop';
import { PostsService } from 'app/posts.services';
@Component({
selector: 'app-cookies-uploader-dialog',
templateUrl: './cookies-uploader-dialog.component.html',
styleUrls: ['./cookies-uploader-dialog.component.scss']
})
export class CookiesUploaderDialogComponent implements OnInit {
public files: NgxFileDropEntry[] = [];
uploading = false;
uploaded = false;
constructor(private postsService: PostsService) { }
ngOnInit(): void {
}
public dropped(files: NgxFileDropEntry[]) {
this.files = files;
this.uploading = false;
this.uploaded = false;
}
uploadFile() {
this.uploading = true;
for (const droppedFile of this.files) {
// Is it a file?
if (droppedFile.fileEntry.isFile) {
const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
fileEntry.file((file: File) => {
// You could upload it like this:
const formData = new FormData()
formData.append('cookies', file, droppedFile.relativePath);
this.postsService.uploadCookiesFile(formData).subscribe(res => {
this.uploading = false;
if (res['success']) {
this.uploaded = true;
this.postsService.openSnackBar('Cookies successfully uploaded!');
}
}, err => {
this.uploading = false;
});
});
}
}
}
public fileOver(event) {
}
public fileLeave(event) {
}
}

@ -30,7 +30,7 @@ export class DownloadItemComponent implements OnInit {
constructor() { } constructor() { }
ngOnInit() { ngOnInit() {
if (this.download && this.download.url && this.download.url.includes('youtube')) { if (this.download && this.download.url && this.download.url.includes('youtu')) {
const string_id = (this.download.is_playlist ? '?list=' : '?v=') const string_id = (this.download.is_playlist ? '?list=' : '?v=')
const index_offset = (this.download.is_playlist ? 6 : 3); const index_offset = (this.download.is_playlist ? 6 : 3);
const end_index = this.download.url.indexOf(string_id) + index_offset; const end_index = this.download.url.indexOf(string_id) + index_offset;

@ -44,6 +44,13 @@ export class FileCardComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.type = this.isAudio ? 'audio' : 'video'; this.type = this.isAudio ? 'audio' : 'video';
if (this.file && this.file.url && this.file.url.includes('youtu')) {
const string_id = (this.isPlaylist ? '?list=' : '?v=')
const index_offset = (this.isPlaylist ? 6 : 3);
const end_index = this.file.url.indexOf(string_id) + index_offset;
this.name = this.file.url.substring(end_index, this.file.url.length);
}
} }
deleteFile(blacklistMode = false) { deleteFile(blacklistMode = false) {

@ -11,10 +11,7 @@
<div class="row"> <div class="row">
<div [ngClass]="allowQualitySelect ? 'col-sm-9' : null" class="col-12"> <div [ngClass]="allowQualitySelect ? 'col-sm-9' : null" class="col-12">
<mat-form-field color="accent" class="example-full-width"> <mat-form-field color="accent" class="example-full-width">
<input style="padding-right: 25px;" matInput (keyup.enter)="downloadClicked()" (ngModelChange)="inputChanged($event)" [(ngModel)]="url" [placeholder]="'URL' + (youtubeSearchEnabled ? ' or search' : '')" type="url" name="url" [formControl]="urlForm" #urlinput> <input style="padding-right: 25px;" matInput (keyup.enter)="downloadClicked()" (ngModelChange)="inputChanged($event)" [(ngModel)]="url" [placeholder]="'URL' + (youtubeSearchEnabled ? ' or search' : '')" type="url" name="url" #urlinput>
<mat-error *ngIf="urlError || urlForm.invalid">
<ng-container i18n="Enter valid URL error">Please enter a valid URL!</ng-container>
</mat-error>
</mat-form-field> </mat-form-field>
<button type="button" class="input-clear-button" mat-icon-button (click)="clearInput()"><mat-icon>clear</mat-icon></button> <button type="button" class="input-clear-button" mat-icon-button (click)="clearInput()"><mat-icon>clear</mat-icon></button>
</div> </div>

@ -73,6 +73,8 @@ export class PostsService implements CanActivate {
this.httpOptions.params = this.httpOptions.params.set('sessionID', this.session_id); this.httpOptions.params = this.httpOptions.params.set('sessionID', this.session_id);
}); });
const login_not_required = this.router.url !== '/player'
// get config // get config
this.loadNavItems().subscribe(res => { this.loadNavItems().subscribe(res => {
const result = !this.debugMode ? res['config_file'] : res; const result = !this.debugMode ? res['config_file'] : res;
@ -84,6 +86,8 @@ export class PostsService implements CanActivate {
this.token = localStorage.getItem('jwt_token'); this.token = localStorage.getItem('jwt_token');
this.httpOptions.params = this.httpOptions.params.set('jwt', this.token); this.httpOptions.params = this.httpOptions.params.set('jwt', this.token);
this.jwtAuth(); this.jwtAuth();
} else if (login_not_required) {
this.setInitialized();
} else { } else {
this.sendToLogin(); this.sendToLogin();
} }
@ -218,6 +222,10 @@ export class PostsService implements CanActivate {
{responseType: 'blob', params: this.httpOptions.params}); {responseType: 'blob', params: this.httpOptions.params});
} }
uploadCookiesFile(fileFormData) {
return this.http.post(this.path + 'uploadCookies', fileFormData, this.httpOptions);
}
downloadArchive(sub) { downloadArchive(sub) {
return this.http.post(this.path + 'downloadArchive', {sub: sub}, {responseType: 'blob', params: this.httpOptions.params}); return this.http.post(this.path + 'downloadArchive', {sub: sub}, {responseType: 'blob', params: this.httpOptions.params});
} }

@ -152,6 +152,10 @@
<mat-checkbox color="accent" [(ngModel)]="new_config['Downloader']['use_youtubedl_archive']"><ng-container i18n="Use youtubedl archive setting">Use youtube-dl archive</ng-container></mat-checkbox> <mat-checkbox color="accent" [(ngModel)]="new_config['Downloader']['use_youtubedl_archive']"><ng-container i18n="Use youtubedl archive setting">Use youtube-dl archive</ng-container></mat-checkbox>
<p>Note: This setting only applies to downloads on the Home page. If you would like to use youtube-dl archive functionality in subscriptions, head down to the Subscriptions section.</p> <p>Note: This setting only applies to downloads on the Home page. If you would like to use youtube-dl archive functionality in subscriptions, head down to the Subscriptions section.</p>
</div> </div>
<div class="col-12 mt-3">
<mat-checkbox color="accent" [(ngModel)]="new_config['Downloader']['safe_download_override']"><ng-container i18n="Safe download override setting">Safe download override</ng-container></mat-checkbox>
</div>
</div> </div>
</div> </div>
</ng-template> </ng-template>
@ -287,6 +291,15 @@
</div> </div>
</div> </div>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<div *ngIf="new_config" class="container-fluid">
<div class="row">
<div class="col-12 mt-4">
<mat-checkbox color="accent" [(ngModel)]="new_config['Advanced']['use_cookies']"><ng-container i18n="Use cookies setting">Use Cookies</ng-container></mat-checkbox>
<button class="checkbox-button" mat-stroked-button (click)="openCookiesUploaderDialog()"><ng-container i18n="Set cookies button">Set Cookies</ng-container></button>
</div>
</div>
</div>
<mat-divider></mat-divider>
<div *ngIf="new_config" class="container-fluid mt-1"> <div *ngIf="new_config" class="container-fluid mt-1">
<app-updater></app-updater> <app-updater></app-updater>
</div> </div>

@ -24,4 +24,10 @@
.text-field { .text-field {
min-width: 30%; min-width: 30%;
}
.checkbox-button {
margin-left: 15px;
margin-bottom: 12px;
bottom: 4px;
} }

@ -8,6 +8,7 @@ import { MatDialog } from '@angular/material/dialog';
import { ArgModifierDialogComponent } from 'app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component'; import { ArgModifierDialogComponent } from 'app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component';
import { CURRENT_VERSION } from 'app/consts'; import { CURRENT_VERSION } from 'app/consts';
import { MatCheckboxChange } from '@angular/material/checkbox'; import { MatCheckboxChange } from '@angular/material/checkbox';
import { CookiesUploaderDialogComponent } from 'app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component';
@Component({ @Component({
selector: 'app-settings', selector: 'app-settings',
@ -156,6 +157,12 @@ export class SettingsComponent implements OnInit {
}); });
} }
openCookiesUploaderDialog() {
this.dialog.open(CookiesUploaderDialogComponent, {
width: '65vw'
});
}
// snackbar helper // snackbar helper
public openSnackBar(message: string, action: string = '') { public openSnackBar(message: string, action: string = '') {
this.snackBar.open(message, action, { this.snackBar.open(message, action, {

@ -13,7 +13,8 @@
"path-audio": "audio/", "path-audio": "audio/",
"path-video": "video/", "path-video": "video/",
"use_youtubedl_archive": false, "use_youtubedl_archive": false,
"custom_args": "" "custom_args": "",
"safe_download_override": false
}, },
"Extra": { "Extra": {
"title_top": "YoutubeDL-Material", "title_top": "YoutubeDL-Material",
@ -37,7 +38,7 @@
"Subscriptions": { "Subscriptions": {
"allow_subscriptions": true, "allow_subscriptions": true,
"subscriptions_base_path": "subscriptions/", "subscriptions_base_path": "subscriptions/",
"subscriptions_check_interval": "300", "subscriptions_check_interval": "30",
"subscriptions_use_youtubedl_archive": true "subscriptions_use_youtubedl_archive": true
}, },
"Users": { "Users": {
@ -49,7 +50,8 @@
"custom_downloading_agent": "", "custom_downloading_agent": "",
"multi_user_mode": true, "multi_user_mode": true,
"allow_advanced_download": true, "allow_advanced_download": true,
"logger_level": "debug" "logger_level": "debug",
"use_cookies": true
} }
} }
} }
Loading…
Cancel
Save