mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-04-15 12:51:28 +03:00
Compare commits
4 Commits
v4.1
...
downloadin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc5fd7e997 | ||
|
|
b7b77995b9 | ||
|
|
d7eb3f35ac | ||
|
|
0ea826487d |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -53,7 +53,6 @@ backend/public/assets/default.json
|
|||||||
backend/subscriptions/channels/*
|
backend/subscriptions/channels/*
|
||||||
backend/subscriptions/playlists/*
|
backend/subscriptions/playlists/*
|
||||||
backend/subscriptions/archives/*
|
backend/subscriptions/archives/*
|
||||||
backend/*.exe
|
|
||||||
src/assets/default.json
|
src/assets/default.json
|
||||||
backend/appdata/db.json
|
backend/appdata/db.json
|
||||||
backend/appdata/archives/archive_audio.txt
|
backend/appdata/archives/archive_audio.txt
|
||||||
@@ -64,4 +63,3 @@ backend/appdata/logs/combined.log
|
|||||||
backend/appdata/logs/error.log
|
backend/appdata/logs/error.log
|
||||||
backend/appdata/users.json
|
backend/appdata/users.json
|
||||||
backend/users/*
|
backend/users/*
|
||||||
backend/appdata/cookies.txt
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ If you'd like to install YoutubeDL-Material, go to the Installation section. If
|
|||||||
|
|
||||||
To deploy, simply clone the repository, and go into the `youtubedl-material` directory. Type `npm install` and all the dependencies will install. Then type `cd backend` and again type `npm install` to install the dependencies for the backend.
|
To deploy, simply clone the repository, and go into the `youtubedl-material` directory. Type `npm install` and all the dependencies will install. Then type `cd backend` and again type `npm install` to install the dependencies for the backend.
|
||||||
|
|
||||||
Once you do that, you're almost up and running. All you need to do is edit the configuration in `youtubedl-material/appdata`, go back into the `youtubedl-material` directory, and type `ng build --prod`. This will build the app, and put the output files in the `youtubedl-material/backend/public` folder.
|
Once you do that, you're almost up and running. All you need to do is edit the configuration in `youtubedl-material/appdata`, go back into the `youtubedl-material` directory, and type `ng build --prod`. This will build the app, and put the output files in the `youtubedl-material/dist` folder. Drag those files into the `public` directory in the `backend` folder.
|
||||||
|
|
||||||
The frontend is now complete. The backend is much easier. Just go into the `backend` folder, and type `npm start`.
|
The frontend is now complete. The backend is much easier. Just go into the `backend` folder, and type `npm start`.
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,18 @@
|
|||||||
FROM alpine:3.12
|
FROM alpine:3.11
|
||||||
|
|
||||||
ENV UID=1000 \
|
RUN \
|
||||||
GID=1000 \
|
apk add --no-cache npm python ffmpeg && \
|
||||||
USER=youtube
|
apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing/ \
|
||||||
|
|
||||||
RUN addgroup -S $USER -g $GID && adduser -D -S $USER -G $USER -u $UID
|
|
||||||
|
|
||||||
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 --chown=$UID:$GID [ "package.json", "package-lock.json", "/app/" ]
|
|
||||||
|
|
||||||
RUN npm install && chown -R $UID:$GID ./
|
COPY package.json /app/
|
||||||
|
|
||||||
COPY --chown=$UID:$GID [ "./", "/app/" ]
|
RUN npm install
|
||||||
|
|
||||||
|
COPY ./ /app/
|
||||||
|
|
||||||
EXPOSE 17442
|
EXPOSE 17442
|
||||||
|
|
||||||
ENTRYPOINT [ "/app/entrypoint.sh" ]
|
|
||||||
CMD [ "node", "app.js" ]
|
CMD [ "node", "app.js" ]
|
||||||
|
|||||||
388
backend/app.js
388
backend/app.js
@@ -13,8 +13,6 @@ var express = require("express");
|
|||||||
var bodyParser = require("body-parser");
|
var bodyParser = require("body-parser");
|
||||||
var archiver = require('archiver');
|
var archiver = require('archiver');
|
||||||
var unzipper = require('unzipper');
|
var unzipper = require('unzipper');
|
||||||
var db_api = require('./db')
|
|
||||||
var utils = require('./utils')
|
|
||||||
var mergeFiles = require('merge-files');
|
var mergeFiles = require('merge-files');
|
||||||
const low = require('lowdb')
|
const low = require('lowdb')
|
||||||
var ProgressBar = require('progress');
|
var ProgressBar = require('progress');
|
||||||
@@ -29,7 +27,6 @@ var config_api = require('./config.js');
|
|||||||
var subscriptions_api = require('./subscriptions')
|
var subscriptions_api = require('./subscriptions')
|
||||||
const CONSTS = require('./consts')
|
const CONSTS = require('./consts')
|
||||||
const { spawn } = require('child_process')
|
const { spawn } = require('child_process')
|
||||||
const read_last_lines = require('read-last-lines');
|
|
||||||
|
|
||||||
const is_windows = process.platform === 'win32';
|
const is_windows = process.platform === 'win32';
|
||||||
|
|
||||||
@@ -76,9 +73,8 @@ const logger = winston.createLogger({
|
|||||||
});
|
});
|
||||||
|
|
||||||
config_api.initialize(logger);
|
config_api.initialize(logger);
|
||||||
|
subscriptions_api.initialize(db, users_db, logger);
|
||||||
auth_api.initialize(users_db, logger);
|
auth_api.initialize(users_db, logger);
|
||||||
db_api.initialize(db, users_db, logger);
|
|
||||||
subscriptions_api.initialize(db, users_db, logger, db_api);
|
|
||||||
|
|
||||||
// var GithubContent = require('github-content');
|
// var GithubContent = require('github-content');
|
||||||
|
|
||||||
@@ -195,12 +191,27 @@ app.use(bodyParser.json());
|
|||||||
// use passport
|
// use passport
|
||||||
app.use(auth_api.passport.initialize());
|
app.use(auth_api.passport.initialize());
|
||||||
|
|
||||||
|
// objects
|
||||||
|
|
||||||
|
function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date) {
|
||||||
|
this.id = id;
|
||||||
|
this.title = title;
|
||||||
|
this.thumbnailURL = thumbnailURL;
|
||||||
|
this.isAudio = isAudio;
|
||||||
|
this.duration = duration;
|
||||||
|
this.url = url;
|
||||||
|
this.uploader = uploader;
|
||||||
|
this.size = size;
|
||||||
|
this.path = path;
|
||||||
|
this.upload_date = upload_date;
|
||||||
|
}
|
||||||
|
|
||||||
// actual functions
|
// actual functions
|
||||||
|
|
||||||
async function checkMigrations() {
|
async function checkMigrations() {
|
||||||
return new Promise(async resolve => {
|
return new Promise(async resolve => {
|
||||||
// 3.5->3.6 migration
|
// 3.5->3.6 migration
|
||||||
const files_to_db_migration_complete = true; // migration phased out! previous code: db.get('files_to_db_migration_complete').value();
|
const files_to_db_migration_complete = db.get('files_to_db_migration_complete').value();
|
||||||
|
|
||||||
if (!files_to_db_migration_complete) {
|
if (!files_to_db_migration_complete) {
|
||||||
logger.info('Beginning migration: 3.5->3.6+')
|
logger.info('Beginning migration: 3.5->3.6+')
|
||||||
@@ -225,7 +236,7 @@ async function runFilesToDBMigration() {
|
|||||||
const file_already_in_db = db.get('files.audio').find({id: file_obj.id}).value();
|
const file_already_in_db = db.get('files.audio').find({id: file_obj.id}).value();
|
||||||
if (!file_already_in_db) {
|
if (!file_already_in_db) {
|
||||||
logger.verbose(`Migrating file ${file_obj.id}`);
|
logger.verbose(`Migrating file ${file_obj.id}`);
|
||||||
db_api.registerFileDB(file_obj.id + '.mp3', 'audio');
|
registerFileDB(file_obj.id + '.mp3', 'audio');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +245,7 @@ async function runFilesToDBMigration() {
|
|||||||
const file_already_in_db = db.get('files.video').find({id: file_obj.id}).value();
|
const file_already_in_db = db.get('files.video').find({id: file_obj.id}).value();
|
||||||
if (!file_already_in_db) {
|
if (!file_already_in_db) {
|
||||||
logger.verbose(`Migrating file ${file_obj.id}`);
|
logger.verbose(`Migrating file ${file_obj.id}`);
|
||||||
db_api.registerFileDB(file_obj.id + '.mp4', 'video');
|
registerFileDB(file_obj.id + '.mp4', 'video');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,8 +605,6 @@ function loadConfigValues() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// empty url defaults to default URL
|
|
||||||
if (!url || url === '') url = 'http://example.com'
|
|
||||||
url_domain = new URL(url);
|
url_domain = new URL(url);
|
||||||
|
|
||||||
let logger_level = config_api.getConfigItem('ytdl_logger_level');
|
let logger_level = config_api.getConfigItem('ytdl_logger_level');
|
||||||
@@ -645,18 +654,8 @@ async function watchSubscriptions() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sub.name) {
|
|
||||||
logger.verbose(`Subscription: skipped check for subscription with uid ${sub.id} as name has not been retrieved yet.`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.verbose('Watching ' + sub.name + ' with delay interval of ' + delay_interval);
|
logger.verbose('Watching ' + sub.name + ' with delay interval of ' + delay_interval);
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const multiUserModeChanged = config_api.getConfigItem('ytdl_multi_user_mode') !== multiUserMode;
|
|
||||||
if (multiUserModeChanged) {
|
|
||||||
logger.verbose(`Skipping subscription ${sub.name} due to multi-user mode change.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await subscriptions_api.getVideosForSub(sub, sub.user_uid);
|
await subscriptions_api.getVideosForSub(sub, sub.user_uid);
|
||||||
subscription_timeouts[sub.id] = false;
|
subscription_timeouts[sub.id] = false;
|
||||||
}, current_delay);
|
}, current_delay);
|
||||||
@@ -701,7 +700,7 @@ function getMp3s() {
|
|||||||
var stats = fs.statSync(file);
|
var stats = fs.statSync(file);
|
||||||
|
|
||||||
var id = file_path.substring(0, file_path.length-4);
|
var id = file_path.substring(0, file_path.length-4);
|
||||||
var jsonobj = utils.getJSONMp3(id, audioFolderPath);
|
var jsonobj = getJSONMp3(id);
|
||||||
if (!jsonobj) continue;
|
if (!jsonobj) continue;
|
||||||
var title = jsonobj.title;
|
var title = jsonobj.title;
|
||||||
var url = jsonobj.webpage_url;
|
var url = jsonobj.webpage_url;
|
||||||
@@ -714,7 +713,7 @@ function getMp3s() {
|
|||||||
var thumbnail = jsonobj.thumbnail;
|
var thumbnail = jsonobj.thumbnail;
|
||||||
var duration = jsonobj.duration;
|
var duration = jsonobj.duration;
|
||||||
var isaudio = true;
|
var isaudio = true;
|
||||||
var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date);
|
var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date);
|
||||||
mp3s.push(file_obj);
|
mp3s.push(file_obj);
|
||||||
}
|
}
|
||||||
return mp3s;
|
return mp3s;
|
||||||
@@ -730,7 +729,7 @@ function getMp4s(relative_path = true) {
|
|||||||
var stats = fs.statSync(file);
|
var stats = fs.statSync(file);
|
||||||
|
|
||||||
var id = file_path.substring(0, file_path.length-4);
|
var id = file_path.substring(0, file_path.length-4);
|
||||||
var jsonobj = utils.getJSONMp4(id, videoFolderPath);
|
var jsonobj = getJSONMp4(id);
|
||||||
if (!jsonobj) continue;
|
if (!jsonobj) continue;
|
||||||
var title = jsonobj.title;
|
var title = jsonobj.title;
|
||||||
var url = jsonobj.webpage_url;
|
var url = jsonobj.webpage_url;
|
||||||
@@ -743,7 +742,7 @@ function getMp4s(relative_path = true) {
|
|||||||
var size = stats.size;
|
var size = stats.size;
|
||||||
|
|
||||||
var isaudio = false;
|
var isaudio = false;
|
||||||
var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date);
|
var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date);
|
||||||
mp4s.push(file_obj);
|
mp4s.push(file_obj);
|
||||||
}
|
}
|
||||||
return mp4s;
|
return mp4s;
|
||||||
@@ -751,14 +750,14 @@ function getMp4s(relative_path = true) {
|
|||||||
|
|
||||||
function getThumbnailMp3(name)
|
function getThumbnailMp3(name)
|
||||||
{
|
{
|
||||||
var obj = utils.getJSONMp3(name, audioFolderPath);
|
var obj = getJSONMp3(name);
|
||||||
var thumbnailLink = obj.thumbnail;
|
var thumbnailLink = obj.thumbnail;
|
||||||
return thumbnailLink;
|
return thumbnailLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getThumbnailMp4(name)
|
function getThumbnailMp4(name)
|
||||||
{
|
{
|
||||||
var obj = utils.getJSONMp4(name, videoFolderPath);
|
var obj = getJSONMp4(name);
|
||||||
var thumbnailLink = obj.thumbnail;
|
var thumbnailLink = obj.thumbnail;
|
||||||
return thumbnailLink;
|
return thumbnailLink;
|
||||||
}
|
}
|
||||||
@@ -795,6 +794,54 @@ function getFileSizeMp4(name)
|
|||||||
return filesize;
|
return filesize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getJSONMp3(name, customPath = null, openReadPerms = false)
|
||||||
|
{
|
||||||
|
var jsonPath = audioFolderPath+name+".info.json";
|
||||||
|
var alternateJsonPath = audioFolderPath+name+".mp3.info.json";
|
||||||
|
if (!customPath) {
|
||||||
|
jsonPath = audioFolderPath + name + ".info.json";
|
||||||
|
} else {
|
||||||
|
jsonPath = customPath + name + ".info.json";
|
||||||
|
alternateJsonPath = customPath + name + ".mp3.info.json";
|
||||||
|
}
|
||||||
|
var obj = null;
|
||||||
|
if (fs.existsSync(jsonPath)) {
|
||||||
|
obj = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
||||||
|
if (!is_windows && openReadPerms) fs.chmodSync(jsonPath, 0o755);
|
||||||
|
}
|
||||||
|
else if (fs.existsSync(alternateJsonPath)) {
|
||||||
|
obj = JSON.parse(fs.readFileSync(alternateJsonPath, 'utf8'));
|
||||||
|
if (!is_windows && openReadPerms) fs.chmodSync(alternateJsonPath, 0o755);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
obj = 0;
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getJSONMp4(name, customPath = null, openReadPerms = false)
|
||||||
|
{
|
||||||
|
var obj = null; // output
|
||||||
|
let jsonPath = null;
|
||||||
|
var alternateJsonPath = videoFolderPath + name + ".mp4.info.json";
|
||||||
|
if (!customPath) {
|
||||||
|
jsonPath = videoFolderPath + name + ".info.json";
|
||||||
|
} else {
|
||||||
|
jsonPath = customPath + name + ".info.json";
|
||||||
|
alternateJsonPath = customPath + name + ".mp4.info.json";
|
||||||
|
}
|
||||||
|
if (fs.existsSync(jsonPath))
|
||||||
|
{
|
||||||
|
obj = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
||||||
|
if (openReadPerms) fs.chmodSync(jsonPath, 0o644);
|
||||||
|
} else if (fs.existsSync(alternateJsonPath)) {
|
||||||
|
obj = JSON.parse(fs.readFileSync(alternateJsonPath, 'utf8'));
|
||||||
|
if (openReadPerms) fs.chmodSync(alternateJsonPath, 0o644);
|
||||||
|
}
|
||||||
|
else obj = 0;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
function getAmountDownloadedMp3(name)
|
function getAmountDownloadedMp3(name)
|
||||||
{
|
{
|
||||||
var partPath = audioFolderPath+name+".mp3.part";
|
var partPath = audioFolderPath+name+".mp3.part";
|
||||||
@@ -887,14 +934,11 @@ async function deleteAudioFile(name, blacklistMode = false) {
|
|||||||
var jsonPath = path.join(audioFolderPath,name+'.mp3.info.json');
|
var jsonPath = path.join(audioFolderPath,name+'.mp3.info.json');
|
||||||
var altJSONPath = path.join(audioFolderPath,name+'.info.json');
|
var altJSONPath = path.join(audioFolderPath,name+'.info.json');
|
||||||
var audioFilePath = path.join(audioFolderPath,name+'.mp3');
|
var audioFilePath = path.join(audioFolderPath,name+'.mp3');
|
||||||
var thumbnailPath = path.join(filePath,name+'.webp');
|
|
||||||
var altThumbnailPath = path.join(filePath,name+'.jpg');
|
|
||||||
jsonPath = path.join(__dirname, jsonPath);
|
jsonPath = path.join(__dirname, jsonPath);
|
||||||
altJSONPath = path.join(__dirname, altJSONPath);
|
altJSONPath = path.join(__dirname, altJSONPath);
|
||||||
audioFilePath = path.join(__dirname, audioFilePath);
|
audioFilePath = path.join(__dirname, audioFilePath);
|
||||||
|
|
||||||
let jsonExists = fs.existsSync(jsonPath);
|
let jsonExists = fs.existsSync(jsonPath);
|
||||||
let thumbnailExists = fs.existsSync(thumbnailPath);
|
|
||||||
|
|
||||||
if (!jsonExists) {
|
if (!jsonExists) {
|
||||||
if (fs.existsSync(altJSONPath)) {
|
if (fs.existsSync(altJSONPath)) {
|
||||||
@@ -903,13 +947,6 @@ async function deleteAudioFile(name, blacklistMode = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!thumbnailExists) {
|
|
||||||
if (fs.existsSync(altThumbnailPath)) {
|
|
||||||
thumbnailExists = true;
|
|
||||||
thumbnailPath = altThumbnailPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let audioFileExists = fs.existsSync(audioFilePath);
|
let audioFileExists = fs.existsSync(audioFilePath);
|
||||||
|
|
||||||
if (config_api.descriptors[name]) {
|
if (config_api.descriptors[name]) {
|
||||||
@@ -928,7 +965,7 @@ async function deleteAudioFile(name, blacklistMode = false) {
|
|||||||
|
|
||||||
// get ID from JSON
|
// get ID from JSON
|
||||||
|
|
||||||
var jsonobj = utils.getJSONMp3(name, audioFolderPath);
|
var jsonobj = getJSONMp3(name);
|
||||||
let id = null;
|
let id = null;
|
||||||
if (jsonobj) id = jsonobj.id;
|
if (jsonobj) id = jsonobj.id;
|
||||||
|
|
||||||
@@ -943,7 +980,6 @@ async function deleteAudioFile(name, blacklistMode = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (jsonExists) fs.unlinkSync(jsonPath);
|
if (jsonExists) fs.unlinkSync(jsonPath);
|
||||||
if (thumbnailExists) fs.unlinkSync(thumbnailPath);
|
|
||||||
if (audioFileExists) {
|
if (audioFileExists) {
|
||||||
fs.unlink(audioFilePath, function(err) {
|
fs.unlink(audioFilePath, function(err) {
|
||||||
if (fs.existsSync(jsonPath) || fs.existsSync(audioFilePath)) {
|
if (fs.existsSync(jsonPath) || fs.existsSync(audioFilePath)) {
|
||||||
@@ -964,30 +1000,12 @@ async function deleteVideoFile(name, customPath = null, blacklistMode = false) {
|
|||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
let filePath = customPath ? customPath : videoFolderPath;
|
let filePath = customPath ? customPath : videoFolderPath;
|
||||||
var jsonPath = path.join(filePath,name+'.info.json');
|
var jsonPath = path.join(filePath,name+'.info.json');
|
||||||
var altJSONPath = path.join(filePath,name+'.mp4.info.json');
|
|
||||||
var videoFilePath = path.join(filePath,name+'.mp4');
|
var videoFilePath = path.join(filePath,name+'.mp4');
|
||||||
var thumbnailPath = path.join(filePath,name+'.webp');
|
|
||||||
var altThumbnailPath = path.join(filePath,name+'.jpg');
|
|
||||||
jsonPath = path.join(__dirname, jsonPath);
|
jsonPath = path.join(__dirname, jsonPath);
|
||||||
videoFilePath = path.join(__dirname, videoFilePath);
|
videoFilePath = path.join(__dirname, videoFilePath);
|
||||||
|
|
||||||
let jsonExists = fs.existsSync(jsonPath);
|
jsonExists = fs.existsSync(jsonPath);
|
||||||
let videoFileExists = fs.existsSync(videoFilePath);
|
videoFileExists = fs.existsSync(videoFilePath);
|
||||||
let thumbnailExists = fs.existsSync(thumbnailPath);
|
|
||||||
|
|
||||||
if (!jsonExists) {
|
|
||||||
if (fs.existsSync(altJSONPath)) {
|
|
||||||
jsonExists = true;
|
|
||||||
jsonPath = altJSONPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!thumbnailExists) {
|
|
||||||
if (fs.existsSync(altThumbnailPath)) {
|
|
||||||
thumbnailExists = true;
|
|
||||||
thumbnailPath = altThumbnailPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config_api.descriptors[name]) {
|
if (config_api.descriptors[name]) {
|
||||||
try {
|
try {
|
||||||
@@ -1005,7 +1023,7 @@ async function deleteVideoFile(name, customPath = null, blacklistMode = false) {
|
|||||||
|
|
||||||
// get ID from JSON
|
// get ID from JSON
|
||||||
|
|
||||||
var jsonobj = utils.getJSONMp4(name, videoFolderPath);
|
var jsonobj = getJSONMp4(name);
|
||||||
let id = null;
|
let id = null;
|
||||||
if (jsonobj) id = jsonobj.id;
|
if (jsonobj) id = jsonobj.id;
|
||||||
|
|
||||||
@@ -1020,7 +1038,6 @@ async function deleteVideoFile(name, customPath = null, blacklistMode = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (jsonExists) fs.unlinkSync(jsonPath);
|
if (jsonExists) fs.unlinkSync(jsonPath);
|
||||||
if (thumbnailExists) fs.unlinkSync(thumbnailPath);
|
|
||||||
if (videoFileExists) {
|
if (videoFileExists) {
|
||||||
fs.unlink(videoFilePath, function(err) {
|
fs.unlink(videoFilePath, function(err) {
|
||||||
if (fs.existsSync(jsonPath) || fs.existsSync(videoFilePath)) {
|
if (fs.existsSync(jsonPath) || fs.existsSync(videoFilePath)) {
|
||||||
@@ -1060,7 +1077,7 @@ function recFindByExt(base,ext,files,result)
|
|||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
function registerFileDB(file_path, type, multiUserMode = null) {
|
function registerFileDB(file_path, type, multiUserMode = null) {
|
||||||
const file_id = file_path.substring(0, file_path.length-4);
|
const file_id = file_path.substring(0, file_path.length-4);
|
||||||
const file_object = generateFileObject(file_id, type, multiUserMode && multiUserMode.file_path);
|
const file_object = generateFileObject(file_id, type, multiUserMode && multiUserMode.file_path);
|
||||||
@@ -1077,7 +1094,7 @@ function registerFileDB(file_path, type, multiUserMode = null) {
|
|||||||
|
|
||||||
if (multiUserMode) {
|
if (multiUserMode) {
|
||||||
auth_api.registerUserFile(multiUserMode.user, file_object, type);
|
auth_api.registerUserFile(multiUserMode.user, file_object, type);
|
||||||
} else if (type === 'audio' || type === 'video') {
|
} else {
|
||||||
// remove existing video if overwriting
|
// remove existing video if overwriting
|
||||||
db.get(`files.${type}`)
|
db.get(`files.${type}`)
|
||||||
.remove({
|
.remove({
|
||||||
@@ -1087,15 +1104,13 @@ function registerFileDB(file_path, type, multiUserMode = null) {
|
|||||||
db.get(`files.${type}`)
|
db.get(`files.${type}`)
|
||||||
.push(file_object)
|
.push(file_object)
|
||||||
.write();
|
.write();
|
||||||
} else if (type == 'subscription') {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return file_object['uid'];
|
return file_object['uid'];
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateFileObject(id, type, customPath = null) {
|
function generateFileObject(id, type, customPath = null) {
|
||||||
var jsonobj = (type === 'audio') ? utils.getJSONMp3(id, customPath, true) : utils.getJSONMp4(id, customPath, true);
|
var jsonobj = (type === 'audio') ? getJSONMp3(id, customPath, true) : getJSONMp4(id, customPath, true);
|
||||||
if (!jsonobj) {
|
if (!jsonobj) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1115,10 +1130,10 @@ function generateFileObject(id, type, customPath = null) {
|
|||||||
var thumbnail = jsonobj.thumbnail;
|
var thumbnail = jsonobj.thumbnail;
|
||||||
var duration = jsonobj.duration;
|
var duration = jsonobj.duration;
|
||||||
var isaudio = type === 'audio';
|
var isaudio = type === 'audio';
|
||||||
var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file_path, upload_date);
|
var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file_path, upload_date);
|
||||||
return file_obj;
|
return file_obj;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
// replaces .webm with appropriate extension
|
// replaces .webm with appropriate extension
|
||||||
function getTrueFileName(unfixed_path, type) {
|
function getTrueFileName(unfixed_path, type) {
|
||||||
let fixed_path = unfixed_path;
|
let fixed_path = unfixed_path;
|
||||||
@@ -1214,7 +1229,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) {
|
|||||||
const download = downloads[session][download_uid];
|
const download = downloads[session][download_uid];
|
||||||
updateDownloads();
|
updateDownloads();
|
||||||
|
|
||||||
youtubedl.exec(url, downloadConfig, {}, function(err, output) {
|
youtubedl.exec(url, downloadConfig, {}, async function(err, output) {
|
||||||
download['downloading'] = false;
|
download['downloading'] = false;
|
||||||
download['timestamp_end'] = Date.now();
|
download['timestamp_end'] = Date.now();
|
||||||
var file_uid = null;
|
var file_uid = null;
|
||||||
@@ -1231,7 +1246,6 @@ 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);
|
||||||
@@ -1272,20 +1286,28 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) {
|
|||||||
}
|
}
|
||||||
let success = NodeID3.write(tags, output_json['_filename']);
|
let success = NodeID3.write(tags, output_json['_filename']);
|
||||||
if (!success) logger.error('Failed to apply ID3 tag to audio file ' + output_json['_filename']);
|
if (!success) logger.error('Failed to apply ID3 tag to audio file ' + output_json['_filename']);
|
||||||
|
} else {
|
||||||
|
const possible_webm_path = removeFileExtension(output_json['_filename']) + '.webm';
|
||||||
|
const possible_mkv_path = removeFileExtension(output_json['_filename']) + '.mkv';
|
||||||
|
const output_file_path = removeFileExtension(output_json['_filename']) + '.mp4';
|
||||||
|
|
||||||
|
// check if video file is not mp4
|
||||||
|
if (fs.existsSync(possible_webm_path)) await convertFileToMp4(possible_webm_path, output_file_path);
|
||||||
|
else if (fs.existsSync(possible_mkv_path)) await convertFileToMp4(possible_mkv_path, output_file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// registers file in DB
|
// registers file in DB
|
||||||
file_uid = db_api.registerFileDB(full_file_path.substring(fileFolderPath.length, full_file_path.length), type, multiUserMode);
|
file_uid = registerFileDB(full_file_path.substring(fileFolderPath.length, full_file_path.length), type, multiUserMode);
|
||||||
|
|
||||||
if (file_name) file_names.push(file_name);
|
if (file_name) file_names.push(file_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_playlist = file_names.length > 1;
|
let is_playlist = file_names.length > 1;
|
||||||
|
|
||||||
if (options.merged_string !== null && options.merged_string !== undefined) {
|
if (options.merged_string) {
|
||||||
let current_merged_archive = fs.readFileSync(path.join(fileFolderPath, `merged_${type}.txt`), 'utf8');
|
let current_merged_archive = fs.readFileSync(fileFolderPath + 'merged.txt', 'utf8');
|
||||||
let diff = current_merged_archive.replace(options.merged_string, '');
|
let diff = current_merged_archive.replace(options.merged_string, '');
|
||||||
const archive_path = options.user ? path.join(fileFolderPath, 'archives', `archive_${type}.txt`) : path.join(archivePath, `archive_${type}.txt`);
|
const archive_path = path.join(archivePath, `archive_${type}.txt`);
|
||||||
fs.appendFileSync(archive_path, diff);
|
fs.appendFileSync(archive_path, diff);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1407,19 +1429,28 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) {
|
|||||||
|
|
||||||
const possible_webm_path = removeFileExtension(video_info['_filename']) + '.webm';
|
const possible_webm_path = removeFileExtension(video_info['_filename']) + '.webm';
|
||||||
const possible_mp4_path = removeFileExtension(video_info['_filename']) + '.mp4';
|
const possible_mp4_path = removeFileExtension(video_info['_filename']) + '.mp4';
|
||||||
// check if audio file is webm
|
const output_file_path = removeFileExtension(video_info['_filename']) + '.mp3';
|
||||||
if (fs.existsSync(possible_webm_path)) await convertFileToMp3(possible_webm_path, video_info['_filename']);
|
// check if audio file is not mp3
|
||||||
else if (fs.existsSync(possible_mp4_path)) await convertFileToMp3(possible_mp4_path, video_info['_filename']);
|
if (fs.existsSync(possible_webm_path)) await convertFileToMp3(possible_webm_path, output_file_path);
|
||||||
|
else if (fs.existsSync(possible_mp4_path)) await convertFileToMp3(possible_mp4_path, output_file_path);
|
||||||
|
} else {
|
||||||
|
const possible_webm_path = removeFileExtension(video_info['_filename']) + '.webm';
|
||||||
|
const possible_mkv_path = removeFileExtension(video_info['_filename']) + '.mkv';
|
||||||
|
const output_file_path = removeFileExtension(video_info['_filename']) + '.mp4';
|
||||||
|
|
||||||
|
// check if video file is not mp4
|
||||||
|
if (fs.existsSync(possible_webm_path)) await convertFileToMp4(possible_webm_path, output_file_path);
|
||||||
|
else if (fs.existsSync(possible_mkv_path)) await convertFileToMp4(possible_mkv_path, output_file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// registers file in DB
|
// registers file in DB
|
||||||
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 = db_api.registerFileDB(base_file_name, type, multiUserMode);
|
file_uid = registerFileDB(base_file_name, type, multiUserMode);
|
||||||
|
|
||||||
if (options.merged_string !== null && options.merged_string !== undefined) {
|
if (options.merged_string) {
|
||||||
let current_merged_archive = fs.readFileSync(path.join(fileFolderPath, `merged_${type}.txt`), 'utf8');
|
let current_merged_archive = fs.readFileSync(fileFolderPath + 'merged.txt', 'utf8');
|
||||||
let diff = current_merged_archive.replace(options.merged_string, '');
|
let diff = current_merged_archive.replace(options.merged_string, '');
|
||||||
const archive_path = options.user ? path.join(fileFolderPath, 'archives', `archive_${type}.txt`) : path.join(archivePath, `archive_${type}.txt`);
|
const archive_path = req.isAuthenticated() ? path.join(fileFolderPath, 'archives', `archive_${type}.txt`) : path.join(archivePath, `archive_${type}.txt`);
|
||||||
fs.appendFileSync(archive_path, diff);
|
fs.appendFileSync(archive_path, diff);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1469,7 +1500,9 @@ async function generateArgs(url, type, options) {
|
|||||||
var youtubePassword = options.youtubePassword;
|
var youtubePassword = options.youtubePassword;
|
||||||
|
|
||||||
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';
|
||||||
|
|
||||||
const is_youtube = url.includes('youtu');
|
const is_youtube = url.includes('youtu');
|
||||||
if (!is_audio && !is_youtube) {
|
if (!is_audio && !is_youtube) {
|
||||||
// tiktok videos fail when using the default format
|
// tiktok videos fail when using the default format
|
||||||
@@ -1492,7 +1525,7 @@ async function generateArgs(url, type, options) {
|
|||||||
if (customOutput) {
|
if (customOutput) {
|
||||||
downloadConfig = ['-o', path.join(fileFolderPath, customOutput) + ".%(ext)s", '--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')), '--write-info-json', '--print-json'];
|
downloadConfig = ['-o', path.join(fileFolderPath, videopath + '.%(ext)s'), qualityPath, '--write-info-json', '--print-json'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qualityPath) downloadConfig.push(qualityPath);
|
if (qualityPath) downloadConfig.push(qualityPath);
|
||||||
@@ -1520,11 +1553,7 @@ 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_folder = options.user ? path.join(fileFolderPath, 'archives') : archivePath;
|
const archive_path = options.user ? path.join(fileFolderPath, 'archives', `archive_${type}.txt`) : path.join(archivePath, `archive_${type}.txt`);
|
||||||
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'));
|
||||||
@@ -1536,7 +1565,7 @@ async function generateArgs(url, type, options) {
|
|||||||
fs.closeSync(fs.openSync(blacklist_path, 'w'));
|
fs.closeSync(fs.openSync(blacklist_path, 'w'));
|
||||||
}
|
}
|
||||||
|
|
||||||
let merged_path = path.join(fileFolderPath, `merged_${type}.txt`);
|
let merged_path = fileFolderPath + 'merged.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];
|
||||||
@@ -1558,8 +1587,8 @@ async function generateArgs(url, type, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
logger.verbose(`youtube-dl args being used: ${downloadConfig.join(',')}`);
|
|
||||||
// downloadConfig.map((arg) => `"${arg}"`);
|
// downloadConfig.map((arg) => `"${arg}"`);
|
||||||
|
logger.verbose(`Generated args: ${downloadConfig.toString()}`);
|
||||||
resolve(downloadConfig);
|
resolve(downloadConfig);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1608,6 +1637,23 @@ async function convertFileToMp3(input_file, output_file) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function convertFileToMp4(input_file, output_file) {
|
||||||
|
logger.verbose(`Converting ${input_file} to ${output_file}...`);
|
||||||
|
return new Promise(resolve => {
|
||||||
|
ffmpeg(input_file).toFormat('mp4')
|
||||||
|
.on('end', () => {
|
||||||
|
logger.verbose(`Conversion for '${output_file}' complete.`);
|
||||||
|
fs.unlinkSync(input_file)
|
||||||
|
resolve(true);
|
||||||
|
})
|
||||||
|
.on('error', (err) => {
|
||||||
|
logger.error('Failed to convert video file to the correct format.');
|
||||||
|
logger.error(err);
|
||||||
|
resolve(false);
|
||||||
|
}).save(output_file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function writeToBlacklist(type, line) {
|
function writeToBlacklist(type, line) {
|
||||||
let blacklistPath = path.join(archivePath, (type === 'audio') ? 'blacklist_audio.txt' : 'blacklist_video.txt');
|
let blacklistPath = path.join(archivePath, (type === 'audio') ? 'blacklist_audio.txt' : 'blacklist_video.txt');
|
||||||
// adds newline to the beginning of the line
|
// adds newline to the beginning of the line
|
||||||
@@ -1728,10 +1774,7 @@ async function autoUpdateYoutubeDL() {
|
|||||||
resolve(true);
|
resolve(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
logger.error('Failed to check youtube-dl version for an update.')
|
|
||||||
logger.error(err)
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1868,23 +1911,6 @@ app.get('/api/using-encryption', function(req, res) {
|
|||||||
res.send(usingEncryption);
|
res.send(usingEncryption);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/logs', async function(req, res) {
|
|
||||||
let logs = null;
|
|
||||||
let lines = req.body.lines;
|
|
||||||
logs_path = path.join('appdata', 'logs', 'combined.log')
|
|
||||||
if (fs.existsSync(logs_path)) {
|
|
||||||
if (lines) logs = await read_last_lines.read(logs_path, lines);
|
|
||||||
else logs = fs.readFileSync(logs_path, 'utf8');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
logger.error(`Failed to find logs file at the expected location: ${logs_path}`)
|
|
||||||
|
|
||||||
res.send({
|
|
||||||
logs: logs,
|
|
||||||
success: !!logs
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post('/api/tomp3', optionalJwt, async function(req, res) {
|
app.post('/api/tomp3', optionalJwt, async function(req, res) {
|
||||||
var url = req.body.url;
|
var url = req.body.url;
|
||||||
var options = {
|
var options = {
|
||||||
@@ -1898,12 +1924,8 @@ 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') || config_api.globalArgsRequiresSafeDownload();
|
|
||||||
if (safeDownloadOverride) logger.verbose('Download is running with the 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);
|
||||||
@@ -1929,12 +1951,9 @@ 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') || config_api.globalArgsRequiresSafeDownload();
|
|
||||||
if (safeDownloadOverride) logger.verbose('Download is running with the safe download override.');
|
|
||||||
const is_playlist = url.includes('playlist');
|
const is_playlist = url.includes('playlist');
|
||||||
|
|
||||||
let result_obj = null;
|
let result_obj = null;
|
||||||
if (safeDownloadOverride || is_playlist || options.customQualityConfiguration || options.customArgs || options.selectedHeight || !url.includes('youtu'))
|
if (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);
|
||||||
@@ -2156,17 +2175,14 @@ app.post('/api/subscribe', optionalJwt, async (req, res) => {
|
|||||||
let url = req.body.url;
|
let url = req.body.url;
|
||||||
let timerange = req.body.timerange;
|
let timerange = req.body.timerange;
|
||||||
let streamingOnly = req.body.streamingOnly;
|
let streamingOnly = req.body.streamingOnly;
|
||||||
let audioOnly = req.body.audioOnly;
|
|
||||||
let customArgs = req.body.customArgs;
|
|
||||||
let customOutput = req.body.customFileOutput;
|
|
||||||
let user_uid = req.isAuthenticated() ? req.user.uid : null;
|
let user_uid = req.isAuthenticated() ? req.user.uid : null;
|
||||||
|
|
||||||
const new_sub = {
|
const new_sub = {
|
||||||
name: name,
|
name: name,
|
||||||
url: url,
|
url: url,
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
streamingOnly: streamingOnly,
|
streamingOnly: streamingOnly,
|
||||||
user_uid: user_uid,
|
user_uid: user_uid
|
||||||
type: audioOnly ? 'audio' : 'video'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// adds timerange if it exists, otherwise all videos will be downloaded
|
// adds timerange if it exists, otherwise all videos will be downloaded
|
||||||
@@ -2174,14 +2190,6 @@ app.post('/api/subscribe', optionalJwt, async (req, res) => {
|
|||||||
new_sub.timerange = timerange;
|
new_sub.timerange = timerange;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customArgs && customArgs !== '') {
|
|
||||||
new_sub.custom_args = customArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customOutput && customOutput !== '') {
|
|
||||||
new_sub.custom_output = customOutput;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result_obj = await subscriptions_api.subscribe(new_sub, user_uid);
|
const result_obj = await subscriptions_api.subscribe(new_sub, user_uid);
|
||||||
|
|
||||||
if (result_obj.success) {
|
if (result_obj.success) {
|
||||||
@@ -2217,11 +2225,10 @@ app.post('/api/unsubscribe', optionalJwt, async (req, res) => {
|
|||||||
app.post('/api/deleteSubscriptionFile', optionalJwt, async (req, res) => {
|
app.post('/api/deleteSubscriptionFile', optionalJwt, async (req, res) => {
|
||||||
let deleteForever = req.body.deleteForever;
|
let deleteForever = req.body.deleteForever;
|
||||||
let file = req.body.file;
|
let file = req.body.file;
|
||||||
let file_uid = req.body.file_uid;
|
|
||||||
let sub = req.body.sub;
|
let sub = req.body.sub;
|
||||||
let user_uid = req.isAuthenticated() ? req.user.uid : null;
|
let user_uid = req.isAuthenticated() ? req.user.uid : null;
|
||||||
|
|
||||||
let success = await subscriptions_api.deleteSubscriptionFile(sub, file, deleteForever, file_uid, user_uid);
|
let success = await subscriptions_api.deleteSubscriptionFile(sub, file, deleteForever, user_uid);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
res.send({
|
res.send({
|
||||||
@@ -2248,49 +2255,45 @@ app.post('/api/getSubscription', optionalJwt, async (req, res) => {
|
|||||||
|
|
||||||
// get sub videos
|
// get sub videos
|
||||||
if (subscription.name && !subscription.streamingOnly) {
|
if (subscription.name && !subscription.streamingOnly) {
|
||||||
var parsed_files = subscription.videos;
|
let base_path = null;
|
||||||
if (!parsed_files) {
|
if (user_uid)
|
||||||
parsed_files = [];
|
base_path = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
|
||||||
let base_path = null;
|
else
|
||||||
if (user_uid)
|
base_path = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||||
base_path = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
|
|
||||||
else
|
|
||||||
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');
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
files = null;
|
files = null;
|
||||||
logger.info('Failed to get folder for subscription: ' + subscription.name + ' at path ' + appended_base_path);
|
logger.info('Failed to get folder for subscription: ' + subscription.name + ' at path ' + appended_base_path);
|
||||||
res.sendStatus(500);
|
res.sendStatus(500);
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
let file = files[i];
|
|
||||||
var file_path = file.substring(appended_base_path.length, file.length);
|
|
||||||
var stats = fs.statSync(file);
|
|
||||||
|
|
||||||
var id = file_path.substring(0, file_path.length-4);
|
|
||||||
var jsonobj = utils.getJSONMp4(id, appended_base_path);
|
|
||||||
if (!jsonobj) continue;
|
|
||||||
var title = jsonobj.title;
|
|
||||||
|
|
||||||
var thumbnail = jsonobj.thumbnail;
|
|
||||||
var duration = jsonobj.duration;
|
|
||||||
var url = jsonobj.webpage_url;
|
|
||||||
var uploader = jsonobj.uploader;
|
|
||||||
var upload_date = jsonobj.upload_date;
|
|
||||||
upload_date = `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}`;
|
|
||||||
var size = stats.size;
|
|
||||||
|
|
||||||
var isaudio = false;
|
|
||||||
var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date);
|
|
||||||
parsed_files.push(file_obj);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
var parsed_files = [];
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
let file = files[i];
|
||||||
|
var file_path = file.substring(appended_base_path.length, file.length);
|
||||||
|
var stats = fs.statSync(file);
|
||||||
|
|
||||||
|
var id = file_path.substring(0, file_path.length-4);
|
||||||
|
var jsonobj = getJSONMp4(id, appended_base_path);
|
||||||
|
if (!jsonobj) continue;
|
||||||
|
var title = jsonobj.title;
|
||||||
|
|
||||||
|
var thumbnail = jsonobj.thumbnail;
|
||||||
|
var duration = jsonobj.duration;
|
||||||
|
var url = jsonobj.webpage_url;
|
||||||
|
var uploader = jsonobj.uploader;
|
||||||
|
var upload_date = jsonobj.upload_date;
|
||||||
|
upload_date = `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}`;
|
||||||
|
var size = stats.size;
|
||||||
|
|
||||||
|
var isaudio = false;
|
||||||
|
var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date);
|
||||||
|
parsed_files.push(file_obj);
|
||||||
|
}
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
subscription: subscription,
|
subscription: subscription,
|
||||||
@@ -2302,7 +2305,7 @@ app.post('/api/getSubscription', optionalJwt, async (req, res) => {
|
|||||||
if (subscription.videos) {
|
if (subscription.videos) {
|
||||||
for (let i = 0; i < subscription.videos.length; i++) {
|
for (let i = 0; i < subscription.videos.length; i++) {
|
||||||
const video = subscription.videos[i];
|
const video = subscription.videos[i];
|
||||||
parsed_files.push(new utils.File(video.title, video.title, video.thumbnail, false, video.duration, video.url, video.uploader, video.size, null, null, video.upload_date));
|
parsed_files.push(new File(video.title, video.title, video.thumbnail, false, video.duration, video.url, video.uploader, video.size, null, null, video.upload_date));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.send({
|
res.send({
|
||||||
@@ -2396,7 +2399,7 @@ app.post('/api/getPlaylist', optionalJwt, async (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/updatePlaylistFiles', optionalJwt, async (req, res) => {
|
app.post('/api/updatePlaylist', optionalJwt, async (req, res) => {
|
||||||
let playlistID = req.body.playlistID;
|
let playlistID = req.body.playlistID;
|
||||||
let fileNames = req.body.fileNames;
|
let fileNames = req.body.fileNames;
|
||||||
let type = req.body.type;
|
let type = req.body.type;
|
||||||
@@ -2404,7 +2407,7 @@ app.post('/api/updatePlaylistFiles', optionalJwt, async (req, res) => {
|
|||||||
let success = false;
|
let success = false;
|
||||||
try {
|
try {
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
auth_api.updatePlaylistFiles(req.user.uid, playlistID, fileNames, type);
|
auth_api.updatePlaylist(req.user.uid, playlistID, fileNames, type);
|
||||||
} else {
|
} else {
|
||||||
db.get(`playlists.${type}`)
|
db.get(`playlists.${type}`)
|
||||||
.find({id: playlistID})
|
.find({id: playlistID})
|
||||||
@@ -2422,14 +2425,6 @@ app.post('/api/updatePlaylistFiles', optionalJwt, async (req, res) => {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/updatePlaylist', optionalJwt, async (req, res) => {
|
|
||||||
let playlist = req.body.playlist;
|
|
||||||
let success = db_api.updatePlaylist(playlist, req.user && req.user.uid);
|
|
||||||
res.send({
|
|
||||||
success: success
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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.playlistID;
|
||||||
let type = req.body.type;
|
let type = req.body.type;
|
||||||
@@ -2550,8 +2545,7 @@ app.post('/api/downloadFile', optionalJwt, async (req, res) => {
|
|||||||
basePath = path.join(usersFileFolder, req.user.uid, 'subscriptions');
|
basePath = path.join(usersFileFolder, req.user.uid, 'subscriptions');
|
||||||
else
|
else
|
||||||
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||||
|
file = path.join(__dirname, basePath, (subscriptionPlaylist === 'true' ? 'playlists' : 'channels'), subscriptionName, fileNames + '.mp4')
|
||||||
file = path.join(__dirname, basePath, (subscriptionPlaylist === 'true' ? 'playlists' : 'channels'), subscriptionName, fileNames + ext);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < fileNames.length; i++) {
|
for (let i = 0; i < fileNames.length; i++) {
|
||||||
@@ -2587,7 +2581,7 @@ app.post('/api/downloadArchive', async (req, res) => {
|
|||||||
let sub = req.body.sub;
|
let sub = req.body.sub;
|
||||||
let archive_dir = sub.archive;
|
let archive_dir = sub.archive;
|
||||||
|
|
||||||
let full_archive_path = path.join(archive_dir, 'archive.txt');
|
let full_archive_path = path.join(__dirname, archive_dir, 'archive.txt');
|
||||||
|
|
||||||
if (fs.existsSync(full_archive_path)) {
|
if (fs.existsSync(full_archive_path)) {
|
||||||
res.sendFile(full_archive_path);
|
res.sendFile(full_archive_path);
|
||||||
@@ -2752,21 +2746,9 @@ app.get('/api/audio/:id', optionalJwt, function(req , res){
|
|||||||
var head;
|
var head;
|
||||||
let id = decodeURIComponent(req.params.id);
|
let id = decodeURIComponent(req.params.id);
|
||||||
let file_path = "audio/" + id + '.mp3';
|
let file_path = "audio/" + id + '.mp3';
|
||||||
let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
|
||||||
let optionalParams = url_api.parse(req.url,true).query;
|
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
if (optionalParams['subName']) {
|
let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
||||||
const isPlaylist = optionalParams['subPlaylist'];
|
file_path = path.join(usersFileFolder, req.user.uid, 'audio', id + '.mp3');
|
||||||
file_path = path.join(usersFileFolder, req.user.uid, 'subscriptions', (isPlaylist === 'true' ? 'playlists/' : 'channels/'),optionalParams['subName'], id + '.mp3')
|
|
||||||
} else {
|
|
||||||
let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
|
||||||
file_path = path.join(usersFileFolder, req.user.uid, 'audio', id + '.mp3');
|
|
||||||
}
|
|
||||||
} else if (optionalParams['subName']) {
|
|
||||||
let basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
|
||||||
const isPlaylist = optionalParams['subPlaylist'];
|
|
||||||
basePath += (isPlaylist === 'true' ? 'playlists/' : 'channels/');
|
|
||||||
file_path = basePath + optionalParams['subName'] + '/' + id + '.mp3';
|
|
||||||
}
|
}
|
||||||
file_path = file_path.replace(/\"/g, '\'');
|
file_path = file_path.replace(/\"/g, '\'');
|
||||||
const stat = fs.statSync(file_path)
|
const stat = fs.statSync(file_path)
|
||||||
@@ -2906,7 +2888,7 @@ app.post('/api/auth/jwtAuth'
|
|||||||
, auth_api.returnAuthResponse
|
, auth_api.returnAuthResponse
|
||||||
);
|
);
|
||||||
app.post('/api/auth/changePassword', optionalJwt, async (req, res) => {
|
app.post('/api/auth/changePassword', optionalJwt, async (req, res) => {
|
||||||
let user_uid = req.body.user_uid;
|
let user_uid = req.user.uid;
|
||||||
let password = req.body.new_password;
|
let password = req.body.new_password;
|
||||||
let success = await auth_api.changeUserPassword(user_uid, password);
|
let success = await auth_api.changeUserPassword(user_uid, password);
|
||||||
res.send({success: success});
|
res.send({success: success});
|
||||||
|
|||||||
@@ -13,8 +13,7 @@
|
|||||||
"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",
|
||||||
|
|||||||
@@ -13,8 +13,7 @@
|
|||||||
"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",
|
||||||
|
|||||||
BIN
backend/aria2c.exe
Normal file
BIN
backend/aria2c.exe
Normal file
Binary file not shown.
@@ -331,7 +331,7 @@ exports.addPlaylist = function(user_uid, new_playlist, type) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.updatePlaylistFiles = function(user_uid, playlistID, new_filenames, type) {
|
exports.updatePlaylist = function(user_uid, playlistID, new_filenames, type) {
|
||||||
users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).find({id: playlistID}).assign({fileNames: new_filenames});
|
users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).find({id: playlistID}).assign({fileNames: new_filenames});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -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 ${type} files. Creating...`);
|
logger.info('Could not find archive file for audio files. Creating...');
|
||||||
fs.ensureFileSync(archive_path);
|
fs.closeSync(fs.openSync(archive_path, 'w'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,13 +155,6 @@ function setConfigItems(items) {
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
function globalArgsRequiresSafeDownload() {
|
|
||||||
const globalArgs = getConfigItem('ytdl_custom_args').split(',,');
|
|
||||||
const argsThatRequireSafeDownload = ['--write-sub', '--write-srt'];
|
|
||||||
const failedArgs = globalArgs.filter(arg => argsThatRequireSafeDownload.includes(arg));
|
|
||||||
return failedArgs && failedArgs.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getConfigItem: getConfigItem,
|
getConfigItem: getConfigItem,
|
||||||
setConfigItem: setConfigItem,
|
setConfigItem: setConfigItem,
|
||||||
@@ -171,8 +164,7 @@ module.exports = {
|
|||||||
configExistsCheck: configExistsCheck,
|
configExistsCheck: configExistsCheck,
|
||||||
CONFIG_ITEMS: CONFIG_ITEMS,
|
CONFIG_ITEMS: CONFIG_ITEMS,
|
||||||
initialize: initialize,
|
initialize: initialize,
|
||||||
descriptors: {},
|
descriptors: {}
|
||||||
globalArgsRequiresSafeDownload: globalArgsRequiresSafeDownload
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_CONFIG = {
|
DEFAULT_CONFIG = {
|
||||||
@@ -190,8 +182,7 @@ 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",
|
||||||
|
|||||||
@@ -40,10 +40,6 @@ 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': {
|
||||||
|
|||||||
119
backend/db.js
119
backend/db.js
@@ -1,119 +0,0 @@
|
|||||||
var fs = require('fs-extra')
|
|
||||||
var path = require('path')
|
|
||||||
var utils = require('./utils')
|
|
||||||
const { uuid } = require('uuidv4');
|
|
||||||
const config_api = require('./config');
|
|
||||||
|
|
||||||
var logger = null;
|
|
||||||
var db = null;
|
|
||||||
var users_db = null;
|
|
||||||
function setDB(input_db, input_users_db) { db = input_db; users_db = input_users_db }
|
|
||||||
function setLogger(input_logger) { logger = input_logger; }
|
|
||||||
|
|
||||||
function initialize(input_db, input_users_db, input_logger) {
|
|
||||||
setDB(input_db, input_users_db);
|
|
||||||
setLogger(input_logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerFileDB(file_path, type, multiUserMode = null, sub = null) {
|
|
||||||
const file_id = file_path.substring(0, file_path.length-4);
|
|
||||||
const file_object = generateFileObject(file_id, type, multiUserMode && multiUserMode.file_path, sub);
|
|
||||||
if (!file_object) {
|
|
||||||
logger.error(`Could not find associated JSON file for ${type} file ${file_id}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
utils.fixVideoMetadataPerms(file_id, type, multiUserMode && multiUserMode.file_path);
|
|
||||||
|
|
||||||
// add additional info
|
|
||||||
file_object['uid'] = uuid();
|
|
||||||
file_object['registered'] = Date.now();
|
|
||||||
path_object = path.parse(file_object['path']);
|
|
||||||
file_object['path'] = path.format(path_object);
|
|
||||||
|
|
||||||
if (!sub) {
|
|
||||||
if (multiUserMode) {
|
|
||||||
const user_uid = multiUserMode.user;
|
|
||||||
users_db.get('users').find({uid: user_uid}).get(`files.${type}`)
|
|
||||||
.remove({
|
|
||||||
path: file_object['path']
|
|
||||||
}).write();
|
|
||||||
|
|
||||||
users_db.get('users').find({uid: user_uid}).get(`files.${type}`)
|
|
||||||
.push(file_object)
|
|
||||||
.write();
|
|
||||||
} else {
|
|
||||||
// remove existing video if overwriting
|
|
||||||
db.get(`files.${type}`)
|
|
||||||
.remove({
|
|
||||||
path: file_object['path']
|
|
||||||
}).write();
|
|
||||||
|
|
||||||
db.get(`files.${type}`)
|
|
||||||
.push(file_object)
|
|
||||||
.write();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sub_db = null;
|
|
||||||
if (multiUserMode) {
|
|
||||||
const user_uid = multiUserMode.user;
|
|
||||||
sub_db = users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id});
|
|
||||||
} else {
|
|
||||||
sub_db = db.get('subscriptions').find({id: sub.id});
|
|
||||||
}
|
|
||||||
sub_db.get('videos').push(file_object).write();
|
|
||||||
}
|
|
||||||
|
|
||||||
return file_object['uid'];
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateFileObject(id, type, customPath = null, sub = null) {
|
|
||||||
if (!customPath && sub) {
|
|
||||||
customPath = getAppendedBasePathSub(sub, config_api.getConfigItem('ytdl_subscriptions_base_path'));
|
|
||||||
}
|
|
||||||
var jsonobj = (type === 'audio') ? utils.getJSONMp3(id, customPath, true) : utils.getJSONMp4(id, customPath, true);
|
|
||||||
if (!jsonobj) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const ext = (type === 'audio') ? '.mp3' : '.mp4'
|
|
||||||
const file_path = utils.getTrueFileName(jsonobj['_filename'], type); // path.join(type === 'audio' ? audioFolderPath : videoFolderPath, id + ext);
|
|
||||||
// console.
|
|
||||||
var stats = fs.statSync(path.join(__dirname, file_path));
|
|
||||||
|
|
||||||
var title = jsonobj.title;
|
|
||||||
var url = jsonobj.webpage_url;
|
|
||||||
var uploader = jsonobj.uploader;
|
|
||||||
var upload_date = jsonobj.upload_date;
|
|
||||||
upload_date = upload_date ? `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}` : 'N/A';
|
|
||||||
|
|
||||||
var size = stats.size;
|
|
||||||
|
|
||||||
var thumbnail = jsonobj.thumbnail;
|
|
||||||
var duration = jsonobj.duration;
|
|
||||||
var isaudio = type === 'audio';
|
|
||||||
var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file_path, upload_date);
|
|
||||||
return file_obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updatePlaylist(playlist, user_uid) {
|
|
||||||
let playlistID = playlist.id;
|
|
||||||
let type = playlist.type;
|
|
||||||
let db_loc = null;
|
|
||||||
if (user_uid) {
|
|
||||||
db_loc = users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).find({id: playlistID});
|
|
||||||
} else {
|
|
||||||
db_loc = db.get(`playlists.${type}`).find({id: playlistID});
|
|
||||||
}
|
|
||||||
db_loc.assign(playlist).write();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAppendedBasePathSub(sub, base_path) {
|
|
||||||
return path.join(base_path, (sub.isPlaylist ? 'playlists/' : 'channels/'), sub.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
initialize: initialize,
|
|
||||||
registerFileDB: registerFileDB,
|
|
||||||
updatePlaylist: updatePlaylist
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#!/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 "$@"
|
|
||||||
BIN
backend/ffmpeg.exe
Normal file
BIN
backend/ffmpeg.exe
Normal file
Binary file not shown.
BIN
backend/ffplay.exe
Normal file
BIN
backend/ffplay.exe
Normal file
Binary file not shown.
BIN
backend/ffprobe.exe
Normal file
BIN
backend/ffprobe.exe
Normal file
Binary file not shown.
@@ -50,7 +50,6 @@
|
|||||||
"passport-jwt": "^4.0.0",
|
"passport-jwt": "^4.0.0",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"progress": "^2.0.3",
|
"progress": "^2.0.3",
|
||||||
"read-last-lines": "^1.7.2",
|
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"unzipper": "^0.10.10",
|
"unzipper": "^0.10.10",
|
||||||
"uuidv4": "^6.0.6",
|
"uuidv4": "^6.0.6",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
backend/public/1-es5.cc1ef452b2945b55327a.js
Normal file
1
backend/public/1-es5.cc1ef452b2945b55327a.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1,198 +0,0 @@
|
|||||||
{
|
|
||||||
"17f0ea5d2d7a262b0e875acc70475f102aee84e6": "Playlist erstellen",
|
|
||||||
"cff1428d10d59d14e45edec3c735a27b5482db59": "Name",
|
|
||||||
"f47e2d56dd8a145b2e9599da9730c049d52962a2": "Audiodateien",
|
|
||||||
"a52dae09be10ca3a65da918533ced3d3f4992238": "Videos",
|
|
||||||
"d9e83ac17026e70ef6e9c0f3240a3b2450367f40": "Youtube-dl Argumente ändern",
|
|
||||||
"7fc1946abe2b40f60059c6cd19975d677095fd19": "Simulierte neue Argumente",
|
|
||||||
"0b71824ae71972f236039bed43f8d2323e8fd570": "Argument hinzufügen",
|
|
||||||
"c8b0e59eb491f2ac7505f0fbab747062e6b32b23": "Nach Kategorie filtern",
|
|
||||||
"9eeb91caef5a50256dd87e1c4b7b3e8216479377": "Argument-Wert verwenden",
|
|
||||||
"25d8ad5eba2ec24e68295a27d6a4bb9b49e3dacd": "Argument-Wert",
|
|
||||||
"7de2451ed3fb8d8b847979bd3f0c740b970f167b": "Argument hinzufügen",
|
|
||||||
"d7b35c384aecd25a516200d6921836374613dfe7": "Abbrechen",
|
|
||||||
"b2623aee44b70c9a4ba1fce16c8a593b0a4c7974": "Ändern",
|
|
||||||
"038ebcb2a89155d90c24fa1c17bfe83dbadc3c20": "YouTube Downloader",
|
|
||||||
"6d2ec8898344c8955542b0542c942038ef28bb80": "Bitte geben Sie eine gültige URL ein.",
|
|
||||||
"a38ae1082fec79ba1f379978337385a539a28e73": "Qualität",
|
|
||||||
"4be966a9dcfbc9b54dfcc604b831c0289f847fa4": "URL verwenden",
|
|
||||||
"d3f02f845e62cebd75fde451ab8479d2a8ad784d": "Ansehen",
|
|
||||||
"4a9889d36910edc8323d7bab60858ab3da6d91df": "Nur Audio",
|
|
||||||
"96a01fafe135afc58b0f8071a4ab00234495ce18": "Multi-Download Modus",
|
|
||||||
"6a21ba5fb0ac804a525bf9ab168038c3ee88e661": "Download",
|
|
||||||
"6a3777f913cf3f288664f0632b9f24794fdcc24e": "Abbrechen",
|
|
||||||
"322ed150e02666fe2259c5b4614eac7066f4ffa0": "Erweitert",
|
|
||||||
"b7ffe7c6586d6f3f18a9246806a7c7d5538ab43e": "Simulierter Befehl:",
|
|
||||||
"4e4c721129466be9c3862294dc40241b64045998": "Benutzerdefinierte Argumente verwenden",
|
|
||||||
"ad2f8ac8b7de7945b80c8e424484da94e597125f": "Benutzerdefinierte Argumente",
|
|
||||||
"a6911c2157f1b775284bbe9654ce5eb30cf45d7f": "Die URL muss nicht angegeben werden, sondern nur der Teil danach. Argumente werden mit zwei Kommata getrennt: ,,",
|
|
||||||
"3a92a3443c65a52f37ca7efb8f453b35dbefbf29": "Benutzerdefinierte Ausgabe verwenden",
|
|
||||||
"d9c02face477f2f9cdaae318ccee5f89856851fb": "Benutzerdefinierte Ausgabe",
|
|
||||||
"fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7": "Dokumentation",
|
|
||||||
"19d1ae64d94d28a29b2c57ae8671aace906b5401": "Der Pfad ist relativ zum Konfigurations-Download-Pfad. Dateiendung auslassen.",
|
|
||||||
"8fad10737d3e3735a6699a4d89cbf6c20f6bb55f": "Authentifizierung verwenden",
|
|
||||||
"08c74dc9762957593b91f6eb5d65efdfc975bf48": "Benutzername",
|
|
||||||
"c32ef07f8803a223a83ed17024b38e8d82292407": "Passwort",
|
|
||||||
"4a0dada6e841a425de3e5006e6a04df26c644fa5": "Audio",
|
|
||||||
"9779715ac05308973d8f1c8658b29b986e92450f": "Ihre Audiodateien befinden sich hier",
|
|
||||||
"47546e45bbb476baaaad38244db444c427ddc502": "Playlisten",
|
|
||||||
"78bd81adb4609b68cfa4c589222bdc233ba1faaa": "Keine Wiedergabelisten verfügbar. Erstellen Sie eine aus Ihren heruntergeladenen Audiodateien, indem Sie auf das blaue Pluszeichen klicken.",
|
|
||||||
"9d2b62bb0b91e2e17fb4177a7e3d6756a2e6ee33": "Video",
|
|
||||||
"960582a8b9d7942716866ecfb7718309728f2916": "Ihre Videodateien befinden sich hier",
|
|
||||||
"0f59c46ca29e9725898093c9ea6b586730d0624e": "Keine Playlisten verfügbar. Erstellen Sie eine aus heruntergeladenen Audiodateien, indem Sie auf das blaue Pluszeichen klicken.",
|
|
||||||
"616e206cb4f25bd5885fc35925365e43cf5fb929": "Name:",
|
|
||||||
"c52db455cca9109ee47e1a612c3f4117c09eb71b": "URL:",
|
|
||||||
"c6eb45d085384903e53ab001a3513d1de6a1dbac": "Kanal:",
|
|
||||||
"109c6f4a5e46efb933612ededfaf52a13178b7e0": "Dateigröße:",
|
|
||||||
"bd630d8669b16e5f264ec4649d9b469fe03e5ff4": "Pfad:",
|
|
||||||
"a67e7d843cef735c79d5ef1c8ba4af3e758912bb": "Hochgeladen am:",
|
|
||||||
"f4e529ae5ffd73001d1ff4bbdeeb0a72e342e5c8": "Schließen",
|
|
||||||
"ca3dbbc7f3e011bffe32a10a3ea45cc84f30ecf1": "ID:",
|
|
||||||
"e684046d73bcee88e82f7ff01e2852789a05fc32": "Anzahl:",
|
|
||||||
"321e4419a943044e674beb55b8039f42a9761ca5": "Info",
|
|
||||||
"826b25211922a1b46436589233cb6f1a163d89b7": "Löschen",
|
|
||||||
"34504b488c24c27e68089be549f0eeae6ebaf30b": "Löschen und zur Blacklist hinzufügen",
|
|
||||||
"121cc5391cd2a5115bc2b3160379ee5b36cd7716": "Einstellungen",
|
|
||||||
"801b98c6f02fe3b32f6afa3ee854c99ed83474e6": "URL",
|
|
||||||
"54c512cca1923ab72faf1a0bd98d3d172469629a": "URL, über die auf diese Applikation zugegriffen wird, ohne Port.",
|
|
||||||
"cb2741a46e3560f6bc6dfd99d385e86b08b26d72": "Port",
|
|
||||||
"22e8f1d0423a3b784fe40fab187b92c06541b577": "Der gewünschte Port. Standard ist 17442.",
|
|
||||||
"d4477669a560750d2064051a510ef4d7679e2f3e": "Multi-User Modus",
|
|
||||||
"2eb03565fcdce7a7a67abc277a936a32fcf51557": "Benutzer Basispfad",
|
|
||||||
"a64505c41150663968e277ec9b3ddaa5f4838798": "Basispfad für Benutzer und deren heruntergeladene Videos.",
|
|
||||||
"cbe16a57be414e84b6a68309d08fad894df797d6": "Verschlüsselung verwenden",
|
|
||||||
"0c1875a79b7ecc792cc1bebca3e063e40b5764f9": "Dateipfad zum Zertifikat",
|
|
||||||
"736551b93461d2de64b118cf4043eee1d1c2cb2c": "Dateipfad zum Zertifikatsschlüssel",
|
|
||||||
"4e3120311801c4acd18de7146add2ee4a4417773": "Abonnements erlauben",
|
|
||||||
"4bee2a4bef2d26d37c9b353c278e24e5cd309ce3": "Abonnements Basispfad",
|
|
||||||
"bc9892814ee2d119ae94378c905ea440a249b84a": "Basispfad für Videos von abonnierten Kanälen und Wiedergabelisten. Dieser ist relativ zum Stammordner von YTDL-Material.",
|
|
||||||
"5bef4b25ba680da7fff06b86a91b1fc7e6a926e3": "Prüfintervall",
|
|
||||||
"0f56a7449b77630c114615395bbda4cab398efd8": "Einheit ist Sekunden, nur Zahlen sind erlaubt.",
|
|
||||||
"78e49b7339b4fa7184dd21bcaae107ce9b7076f6": "Youtube-DL Archiv verwenden",
|
|
||||||
"fa9fe4255231dd1cc6b29d3d254a25cb7c764f0f": "Mit der Archivfunktion",
|
|
||||||
"09006404cccc24b7a8f8d1ce0b39f2761ab841d8": "werden Informationen über Videos, welche durch ein Abonnement heruntergeladen wurden, in einem Textdokument festgehalten. Diese befinden sich in dem Archiv Unterverzeichnis vom Abonnementsordner.",
|
|
||||||
"29ed79a98fc01e7f9537777598e31dbde3aa7981": "Dadurch können Videos permanent gelöscht werden, ohne das Abonnement beenden zu müssen. Außerdem kann dadurch aufgezeichnet werden, welche Videos heruntergeladen wurden. Z. B. im Falle eines Datenverlusts.",
|
|
||||||
"27a56aad79d8b61269ed303f11664cc78bcc2522": "Design",
|
|
||||||
"ff7cee38a2259526c519f878e71b964f41db4348": "Standard",
|
|
||||||
"adb4562d2dbd3584370e44496969d58c511ecb63": "Dunkel",
|
|
||||||
"7a6bacee4c31cb5c0ac2d24274fb4610d8858602": "Designänderung erlauben",
|
|
||||||
"fe46ccaae902ce974e2441abe752399288298619": "Sprache",
|
|
||||||
"82421c3e46a0453a70c42900eab51d58d79e6599": "Allgemein",
|
|
||||||
"ab2756805742e84ad0cc0468f4be2d8aa9f855a5": "Audio Basispfad",
|
|
||||||
"c2c89cdf45d46ea64d2ed2f9ac15dfa4d77e26ca": "Dateipfad für Audio-Downloads. Dieser ist relativ zum Stammordner von YTDL-Material.",
|
|
||||||
"46826331da1949bd6fb74624447057099c9d20cd": "Video Basispfad",
|
|
||||||
"17c92e6d47a213fa95b5aa344b3f258147123f93": "Dateipfad für Video-Downloads. Dieser ist relativ zum Stammordner von YTDL-Material.",
|
|
||||||
"6b995e7130b4d667eaab6c5f61b362ace486d26d": "Globale benutzerdefinierte Argumente für Downloads auf der Startseite. Argumente werden durch zwei Kommata voneinander getrennt: ,,",
|
|
||||||
"0ba25ad86a240576c4f20a2fada4722ebba77b1e": "Downloader",
|
|
||||||
"61f8fd90b5f8cb20c70371feb2ee5e1fac5a9095": "Titel der Kopfzeile",
|
|
||||||
"78d3531417c0d4ba4c90f0d4ae741edc261ec8df": "Dateimanager aktivieren",
|
|
||||||
"a5a1be0a5df07de9eec57f5d2a86ed0204b2e75a": "Download-Manager aktivieren",
|
|
||||||
"c33bd5392b39dbed36b8e5a1145163a15d45835f": "Qualitätsauswahl erlauben",
|
|
||||||
"bda5508e24e0d77debb28bcd9194d8fefb1cfb92": "Nur Download Modus",
|
|
||||||
"09d31c803a7252658694e1e3176b97f5655a3fe3": "Multi-Download Modus erlauben",
|
|
||||||
"d8b47221b5af9e9e4cd5cb434d76fc0c91611409": "Einstellungen durch PIN schützen",
|
|
||||||
"f5ec7b2cdf87d41154f4fcbc86e856314409dcb9": "Neuen PIN festlegen",
|
|
||||||
"1c4dbce56d96b8974aac24a02f7ab2ee81415014": "Öffentliche API aktivieren",
|
|
||||||
"23bd81dcc30b74d06279a26d7a42e8901c1b124e": "Öffentlicher API-Schlüssel",
|
|
||||||
"41016a73d8ad85e6cb26dffa0a8fab9fe8f60d8e": "Dokumentation ansehen",
|
|
||||||
"1b258b258b4cc475ceb2871305b61756b0134f4a": "Generieren",
|
|
||||||
"d5d7c61349f3b0859336066e6d453fc35d334fe5": "YouTube API verwenden",
|
|
||||||
"ce10d31febb3d9d60c160750570310f303a22c22": "Youtube API-Schlüssel",
|
|
||||||
"8602e313cdfa7c4cc475ccbe86459fce3c3fd986": "Schlüsselgeneration ist einfach!",
|
|
||||||
"9b3cedfa83c6d7acb3210953289d1be4aab115c7": "Hier klicken",
|
|
||||||
"7f09776373995003161235c0c8d02b7f91dbc4df": "um die offizielle YoutubeDL-Material Chrome-Erweiterung manuell herunterzuladen.",
|
|
||||||
"5b5296423906ab3371fdb2b5a5aaa83acaa2ee52": "Die Erweiterung muss manuell installiert werden und in den Einstellungen der Erweiterung muss die Frontend-URL eingetragen werden.",
|
|
||||||
"9a2ec6da48771128384887525bdcac992632c863": "um die offizielle YoutubeDL-Material Firefox-Erweiterung direkt aus dem Firefox-Addon-Store zu installieren.",
|
|
||||||
"eb81be6b49e195e5307811d1d08a19259d411f37": "Detaillierte Anleitung.",
|
|
||||||
"cb17ff8fe3961cf90f44bee97c88a3f3347a7e55": "Die Frontend-URL muss in den Einstellungen der Erweiterung eingetragen werden.",
|
|
||||||
"61b81b11aad0b9d970ece2fce18405f07eac69c2": "Der untenstehende Link muss nur in die Lesezeichenleiste gezogen werden. Auf einer unterstützten Webseite können Sie danach einfach auf das Lesezeichen klicken, um das Video herunterzuladen.",
|
|
||||||
"c505d6c5de63cc700f0aaf8a4b31fae9e18024e5": "'Nur Audio' Lesezeichen generieren",
|
|
||||||
"d5f69691f9f05711633128b5a3db696783266b58": "Extra",
|
|
||||||
"5fab47f146b0a4b809dcebf3db9da94df6299ea1": "Standard Download-Agent verwenden",
|
|
||||||
"ec71e08aee647ea4a71fd6b7510c54d84a797ca6": "Downloader auswählen",
|
|
||||||
"dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8": "Erweiterte Download-Optionen aktivieren",
|
|
||||||
"bc2e854e111ecf2bd7db170da5e3c2ed08181d88": "Erweitert",
|
|
||||||
"37224420db54d4bc7696f157b779a7225f03ca9d": "Benutzerregistrierung zulassen",
|
|
||||||
"4d13a9cd5ed3dcee0eab22cb25198d43886942be": "Benutzer",
|
|
||||||
"52c9a103b812f258bcddc3d90a6e3f46871d25fe": "Speichern",
|
|
||||||
"fe8fd36dbf5deee1d56564965787a782a66eba44": "{VAR_SELECT, select, true {Schließen} false {Abbrechen} other {Andere}}",
|
|
||||||
"cec82c0a545f37420d55a9b6c45c20546e82f94e": "Über YoutubeDL-Material",
|
|
||||||
"199c17e5d6a419313af3c325f06dcbb9645ca618": "ist ein Open-Source YouTube-Downloader, der nach den Material-Design-Richtlinien von Google erstellt wurde. Sie können Ihre Lieblingsvideos reibungslos als Video- oder Audiodateien herunterladen und sogar Ihre Lieblingskanäle und Wiedergabelisten abonnieren, um auf dem Laufenden zu bleiben.",
|
|
||||||
"bc0ad0ee6630acb7fcb7802ec79f5a0ee943c1a7": "beinhaltet viele tolle Funktionen! API, Docker und Lokalisierung werden unter anderem unterstützt. Informieren Sie sich über alle unterstützten Funktionen auf Github.",
|
|
||||||
"a45e3b05f0529dc5246d70ef62304c94426d4c81": "Installierte Version:",
|
|
||||||
"e22f3a5351944f3a1a10cfc7da6f65dfbe0037fe": "Suche nach Updates ...",
|
|
||||||
"a16e92385b4fd9677bb830a4b796b8b79c113290": "Aktualisierung verfügbar",
|
|
||||||
"189b28aaa19b3c51c6111ad039c4fd5e2a22e370": "Sie können über das Einstellungsmenü aktualisieren.",
|
|
||||||
"b33536f59b94ec935a16bd6869d836895dc5300c": "Haben Sie einen Fehler gefunden oder einen Vorschlag?",
|
|
||||||
"e1f398f38ff1534303d4bb80bd6cece245f24016": "um ein Ticket zu öffnen.",
|
|
||||||
"42ff677ec14f111e88bd6cdd30145378e994d1bf": "Ihr Profil",
|
|
||||||
"ac9d09de42edca1296371e4d801349c9096ac8de": "UID:",
|
|
||||||
"a5ed099ffc9e96f6970df843289ade8a7d20ab9f": "Erstellt:",
|
|
||||||
"fa96f2137af0a24e6d6d54c598c0af7d5d5ad344": "Sie sind nicht angemeldet.",
|
|
||||||
"6765b4c916060f6bc42d9bb69e80377dbcb5e4e9": "Anmelden",
|
|
||||||
"bb694b49d408265c91c62799c2b3a7e3151c824d": "Ausloggen",
|
|
||||||
"a1dbca87b9f36d2b06a5cbcffb5814c4ae9b798a": "Admin-Konto erstellen",
|
|
||||||
"2d2adf3ca26a676bca2269295b7455a26fd26980": "Es wurde kein Standard-Administratorkonto erkannt. Ein Administratorkonto mit dem Benutzernamen \"admin\" wird erstellt und ein Passwort wird festgelegt.",
|
|
||||||
"70a67e04629f6d412db0a12d51820b480788d795": "Erstellen",
|
|
||||||
"994363f08f9fbfa3b3994ff7b35c6904fdff18d8": "Profil",
|
|
||||||
"004b222ff9ef9dd4771b777950ca1d0e4cd4348a": "Über",
|
|
||||||
"92eee6be6de0b11c924e3ab27db30257159c0a7c": "Startseite",
|
|
||||||
"357064ca9d9ac859eb618e28e8126fa32be049e2": "Abonnements",
|
|
||||||
"822fab38216f64e8166d368b59fe756ca39d301b": "Downloads",
|
|
||||||
"a249a5ae13e0835383885aaf697d2890cc3e53e9": "Playlist teilen",
|
|
||||||
"15da89490e04496ca9ea1e1b3d44fb5efd4a75d9": "Video teilen",
|
|
||||||
"1d540dcd271b316545d070f9d182c372d923aadd": "Audio teilen",
|
|
||||||
"1f6d14a780a37a97899dc611881e6bc971268285": "Freigabe aktivieren",
|
|
||||||
"6580b6a950d952df847cb3d8e7176720a740adc8": "Zeitstempel verwenden",
|
|
||||||
"4f2ed9e71a7c981db3e50ae2fedb28aff2ec4e6c": "Sekunden",
|
|
||||||
"3a6e5a6aa78ca864f6542410c5dafb6334538106": "In die Zwischenablage kopieren",
|
|
||||||
"5b3075e8dc3f3921ec316b0bd83b6d14a06c1a4f": "Änderungen speichern",
|
|
||||||
"4f8b2bb476981727ab34ed40fde1218361f92c45": "Details",
|
|
||||||
"e9aff8e6df2e2bf6299ea27bb2894c70bc48bd4d": "Ein Fehler ist aufgetreten:",
|
|
||||||
"77b0c73840665945b25bd128709aa64c8f017e1c": "Download Start:",
|
|
||||||
"08ff9375ec078065bcdd7637b7ea65fce2979266": "Download Ende:",
|
|
||||||
"ad127117f9471612f47d01eae09709da444a36a4": "Dateipfad(e):",
|
|
||||||
"a9806cf78ce00eb2613eeca11354a97e033377b8": "Abonnieren Sie eine Playlist oder einen Kanal",
|
|
||||||
"93efc99ae087fc116de708ecd3ace86ca237cf30": "Playlist oder Kanal URL",
|
|
||||||
"08f5d0ef937ae17feb1b04aff15ad88911e87baf": "Benutzerdefinierter Name",
|
|
||||||
"f3f62aa84d59f3a8b900cc9a7eec3ef279a7b4e7": "Dies ist optional",
|
|
||||||
"ea30873bd3f0d5e4fb2378eec3f0a1db77634a28": "Alle Uploads herunterladen",
|
|
||||||
"28a678e9cabf86e44c32594c43fa0e890135c20f": "Videos herunterladen aus den letzten",
|
|
||||||
"408ca4911457e84a348cecf214f02c69289aa8f1": "Nur Streaming Modus",
|
|
||||||
"d0336848b0c375a1c25ba369b3481ee383217a4f": "Abonnieren",
|
|
||||||
"e78c0d60ac39787f62c9159646fe0b3c1ed55a1d": "Typ:",
|
|
||||||
"a44d86aa1e6c20ced07aca3a7c081d8db9ded1c6": "Archiv:",
|
|
||||||
"8efc77bf327659c0fec1f518cf48a98cdcd9dddf": "Archiv exportieren",
|
|
||||||
"3042bd3ad8dffcfeca5fd1ae6159fd1047434e95": "Deabonnieren",
|
|
||||||
"e2319dec5b4ccfb6ed9f55ccabd63650a8fdf547": "Ihre Abonnements",
|
|
||||||
"807cf11e6ac1cde912496f764c176bdfdd6b7e19": "Kanäle",
|
|
||||||
"29b89f751593e1b347eef103891b7a1ff36ec03f": "Name nicht verfügbar. Kanal wird abgerufen...",
|
|
||||||
"4636cd4a1379c50d471e98786098c4d39e1e82ad": "Sie haben keine Kanäle abonniert.",
|
|
||||||
"2e0a410652cb07d069f576b61eab32586a18320d": "Name nicht verfügbar. Playlist wird abgerufen...",
|
|
||||||
"587b57ced54965d8874c3fd0e9dfedb987e5df04": "Sie haben keine Playlisten abonniert.",
|
|
||||||
"7e892ba15f2c6c17e83510e273b3e10fc32ea016": "Suchen",
|
|
||||||
"2054791b822475aeaea95c0119113de3200f5e1c": "Länge:",
|
|
||||||
"94e01842dcee90531caa52e4147f70679bac87fe": "Löschen und erneut herunterladen",
|
|
||||||
"2031adb51e07a41844e8ba7704b054e98345c9c1": "Permanent löschen",
|
|
||||||
"91ecce65f1d23f9419d1c953cd6b7bc7f91c110e": "Updater",
|
|
||||||
"1372e61c5bd06100844bd43b98b016aabc468f62": "Wählen Sie eine Version:",
|
|
||||||
"cfc2f436ec2beffb042e7511a73c89c372e86a6c": "Registrieren",
|
|
||||||
"a1ad8b1be9be43b5183bd2c3186d4e19496f2a0b": "Sitzungs-ID:",
|
|
||||||
"eb98135e35af26a9a326ee69bd8ff104d36dd8ec": "(aktuell)",
|
|
||||||
"7117fc42f860e86d983bfccfcf2654e5750f3406": "Zurzeit sind keine Downloads verfügbar.",
|
|
||||||
"b7ff2e2b909c53abe088fe60b9f4b6ac7757247f": "Nutzer registrieren",
|
|
||||||
"024886ca34a6f309e3e51c2ed849320592c3faaa": "Benutzername",
|
|
||||||
"2bd201aea09e43fbfd3cd15ec0499b6755302329": "Benutzer verwalten",
|
|
||||||
"29c97c8e76763bb15b6d515648fa5bd1eb0f7510": "Benutzer-UID",
|
|
||||||
"e70e209561583f360b1e9cefd2cbb1fe434b6229": "Neues Passwort",
|
|
||||||
"6498fa1b8f563988f769654a75411bb8060134b9": "Neues Passwort festlegen",
|
|
||||||
"40da072004086c9ec00d125165da91eaade7f541": "Standard verwenden",
|
|
||||||
"4f20f2d5a6882190892e58b85f6ccbedfa737952": "Ja",
|
|
||||||
"3d3ae7deebc5949b0c1c78b9847886a94321d9fd": "Nein",
|
|
||||||
"57c6c05d8ebf4ef1180c2705033c044f655bb2c4": "Rolle verwalten",
|
|
||||||
"746f64ddd9001ac456327cd9a3d5152203a4b93c": "Benutzername",
|
|
||||||
"52c1447c1ec9570a2a3025c7e566557b8d19ed92": "Rolle",
|
|
||||||
"59a8c38db3091a63ac1cb9590188dc3a972acfb3": "Aktionen",
|
|
||||||
"4d92a0395dd66778a931460118626c5794a3fc7a": "Benutzer hinzufügen",
|
|
||||||
"b0d7dd8a1b0349622d6e0c6e643e24a9ea0efa1d": "Rolle bearbeiten"
|
|
||||||
}
|
|
||||||
@@ -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.1f02852de81190376ba1.js" type="module"></script><script src="runtime-es5.1f02852de81190376ba1.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>
|
<script src="runtime-es2015.06b6262a0d981fd4885e.js" type="module"></script><script src="runtime-es5.06b6262a0d981fd4885e.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>
|
||||||
|
|||||||
@@ -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:"6bc1f7cd24dfb6add92c"}[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:"cc1ef452b2945b55327a"}[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:"6bc1f7cd24dfb6add92c"}[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:"cc1ef452b2945b55327a"}[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()}([]);
|
||||||
@@ -6,20 +6,17 @@ var path = require('path');
|
|||||||
|
|
||||||
var youtubedl = require('youtube-dl');
|
var youtubedl = require('youtube-dl');
|
||||||
const config_api = require('./config');
|
const config_api = require('./config');
|
||||||
var utils = require('./utils')
|
|
||||||
|
|
||||||
const debugMode = process.env.YTDL_MODE === 'debug';
|
const debugMode = process.env.YTDL_MODE === 'debug';
|
||||||
|
|
||||||
var logger = null;
|
var logger = null;
|
||||||
var db = null;
|
var db = null;
|
||||||
var users_db = null;
|
var users_db = null;
|
||||||
var db_api = null;
|
function setDB(input_db, input_users_db) { db = input_db; users_db = input_users_db }
|
||||||
|
|
||||||
function setDB(input_db, input_users_db, input_db_api) { db = input_db; users_db = input_users_db; db_api = input_db_api }
|
|
||||||
function setLogger(input_logger) { logger = input_logger; }
|
function setLogger(input_logger) { logger = input_logger; }
|
||||||
|
|
||||||
function initialize(input_db, input_users_db, input_logger, input_db_api) {
|
function initialize(input_db, input_users_db, input_logger) {
|
||||||
setDB(input_db, input_users_db, input_db_api);
|
setDB(input_db, input_users_db);
|
||||||
setLogger(input_logger);
|
setLogger(input_logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +28,6 @@ async function subscribe(sub, user_uid = null) {
|
|||||||
return new Promise(async resolve => {
|
return new Promise(async resolve => {
|
||||||
// sub should just have url and name. here we will get isPlaylist and path
|
// sub should just have url and name. here we will get isPlaylist and path
|
||||||
sub.isPlaylist = sub.url.includes('playlist');
|
sub.isPlaylist = sub.url.includes('playlist');
|
||||||
sub.videos = [];
|
|
||||||
|
|
||||||
let url_exists = false;
|
let url_exists = false;
|
||||||
|
|
||||||
@@ -48,25 +44,15 @@ async function subscribe(sub, user_uid = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add sub to db
|
// add sub to db
|
||||||
let sub_db = null;
|
if (user_uid)
|
||||||
if (user_uid) {
|
|
||||||
users_db.get('users').find({uid: user_uid}).get('subscriptions').push(sub).write();
|
users_db.get('users').find({uid: user_uid}).get('subscriptions').push(sub).write();
|
||||||
sub_db = users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id});
|
else
|
||||||
} else {
|
|
||||||
db.get('subscriptions').push(sub).write();
|
db.get('subscriptions').push(sub).write();
|
||||||
sub_db = db.get('subscriptions').find({id: sub.id});
|
|
||||||
}
|
|
||||||
let success = await getSubscriptionInfo(sub, user_uid);
|
let success = await getSubscriptionInfo(sub, user_uid);
|
||||||
|
|
||||||
if (success) {
|
|
||||||
sub = sub_db.value();
|
|
||||||
getVideosForSub(sub, user_uid);
|
|
||||||
} else {
|
|
||||||
logger.error('Subscribe: Failed to get subscription info. Subscribe failed.')
|
|
||||||
};
|
|
||||||
|
|
||||||
result_obj.success = success;
|
result_obj.success = success;
|
||||||
result_obj.sub = sub;
|
result_obj.sub = sub;
|
||||||
|
getVideosForSub(sub, user_uid);
|
||||||
resolve(result_obj);
|
resolve(result_obj);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -174,33 +160,25 @@ async function unsubscribe(sub, deleteMode, user_uid = null) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSubscriptionFile(sub, file, deleteForever, file_uid = null, user_uid = null) {
|
async function deleteSubscriptionFile(sub, file, deleteForever, user_uid = null) {
|
||||||
let basePath = null;
|
let basePath = null;
|
||||||
let sub_db = null;
|
if (user_uid)
|
||||||
if (user_uid) {
|
|
||||||
basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
|
basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
|
||||||
sub_db = users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id});
|
else
|
||||||
} else {
|
|
||||||
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||||
sub_db = db.get('subscriptions').find({id: sub.id});
|
|
||||||
}
|
|
||||||
const useArchive = config_api.getConfigItem('ytdl_subscriptions_use_youtubedl_archive');
|
const useArchive = config_api.getConfigItem('ytdl_subscriptions_use_youtubedl_archive');
|
||||||
const appendedBasePath = getAppendedBasePath(sub, basePath);
|
const appendedBasePath = getAppendedBasePath(sub, basePath);
|
||||||
const name = file;
|
const name = file;
|
||||||
let retrievedID = null;
|
let retrievedID = null;
|
||||||
sub_db.get('videos').remove({uid: file_uid}).write();
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
let filePath = appendedBasePath;
|
let filePath = appendedBasePath;
|
||||||
const ext = (sub.type && sub.type === 'audio') ? '.mp3' : '.mp4'
|
|
||||||
var jsonPath = path.join(__dirname,filePath,name+'.info.json');
|
var jsonPath = path.join(__dirname,filePath,name+'.info.json');
|
||||||
var videoFilePath = path.join(__dirname,filePath,name+ext);
|
var videoFilePath = path.join(__dirname,filePath,name+'.mp4');
|
||||||
var imageFilePath = path.join(__dirname,filePath,name+'.jpg');
|
var imageFilePath = path.join(__dirname,filePath,name+'.jpg');
|
||||||
var altImageFilePath = path.join(__dirname,filePath,name+'.jpg');
|
|
||||||
|
|
||||||
jsonExists = fs.existsSync(jsonPath);
|
jsonExists = fs.existsSync(jsonPath);
|
||||||
videoFileExists = fs.existsSync(videoFilePath);
|
videoFileExists = fs.existsSync(videoFilePath);
|
||||||
imageFileExists = fs.existsSync(imageFilePath);
|
imageFileExists = fs.existsSync(imageFilePath);
|
||||||
altImageFileExists = fs.existsSync(altImageFilePath);
|
|
||||||
|
|
||||||
if (jsonExists) {
|
if (jsonExists) {
|
||||||
retrievedID = JSON.parse(fs.readFileSync(jsonPath, 'utf8'))['id'];
|
retrievedID = JSON.parse(fs.readFileSync(jsonPath, 'utf8'))['id'];
|
||||||
@@ -211,10 +189,6 @@ async function deleteSubscriptionFile(sub, file, deleteForever, file_uid = null,
|
|||||||
fs.unlinkSync(imageFilePath);
|
fs.unlinkSync(imageFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (altImageFileExists) {
|
|
||||||
fs.unlinkSync(altImageFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (videoFileExists) {
|
if (videoFileExists) {
|
||||||
fs.unlink(videoFilePath, function(err) {
|
fs.unlink(videoFilePath, function(err) {
|
||||||
if (fs.existsSync(jsonPath) || fs.existsSync(videoFilePath)) {
|
if (fs.existsSync(jsonPath) || fs.existsSync(videoFilePath)) {
|
||||||
@@ -263,45 +237,13 @@ async function getVideosForSub(sub, user_uid = null) {
|
|||||||
const useArchive = config_api.getConfigItem('ytdl_subscriptions_use_youtubedl_archive');
|
const useArchive = config_api.getConfigItem('ytdl_subscriptions_use_youtubedl_archive');
|
||||||
|
|
||||||
let appendedBasePath = null
|
let appendedBasePath = null
|
||||||
appendedBasePath = getAppendedBasePath(sub, basePath);
|
if (sub.name) {
|
||||||
|
appendedBasePath = getAppendedBasePath(sub, basePath);
|
||||||
let multiUserMode = null;
|
|
||||||
if (user_uid) {
|
|
||||||
multiUserMode = {
|
|
||||||
user: user_uid,
|
|
||||||
file_path: appendedBasePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ext = (sub.type && sub.type === 'audio') ? '.mp3' : '.mp4'
|
|
||||||
|
|
||||||
let fullOutput = appendedBasePath + '/%(title)s' + ext;
|
|
||||||
if (sub.custom_output) {
|
|
||||||
fullOutput = appendedBasePath + '/' + sub.custom_output + ext;
|
|
||||||
}
|
|
||||||
|
|
||||||
let downloadConfig = ['-o', fullOutput, '-ciw', '--write-info-json', '--print-json'];
|
|
||||||
|
|
||||||
let qualityPath = null;
|
|
||||||
if (sub.type && sub.type === 'audio') {
|
|
||||||
qualityPath = ['-f', 'bestaudio']
|
|
||||||
qualityPath.push('-x');
|
|
||||||
qualityPath.push('--audio-format', 'mp3');
|
|
||||||
} else {
|
} else {
|
||||||
qualityPath = ['-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4']
|
appendedBasePath = path.join(basePath, (sub.isPlaylist ? 'playlists/%(playlist_title)s' : 'channels/%(uploader)s'));
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadConfig.push(...qualityPath)
|
let downloadConfig = ['-o', appendedBasePath + '/%(title)s.mp4', '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4', '-ciw', '--write-info-json', '--print-json'];
|
||||||
|
|
||||||
if (sub.custom_args) {
|
|
||||||
customArgsArray = sub.custom_args.split(',,');
|
|
||||||
if (customArgsArray.indexOf('-f') !== -1) {
|
|
||||||
// if custom args has a custom quality, replce the original quality with that of custom args
|
|
||||||
const original_output_index = downloadConfig.indexOf('-f');
|
|
||||||
downloadConfig.splice(original_output_index, 2);
|
|
||||||
}
|
|
||||||
downloadConfig.push(...customArgsArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
let archive_dir = null;
|
let archive_dir = null;
|
||||||
let archive_path = null;
|
let archive_path = null;
|
||||||
@@ -344,7 +286,7 @@ async function getVideosForSub(sub, user_uid = null) {
|
|||||||
const outputs = err.stdout.split(/\r\n|\r|\n/);
|
const outputs = err.stdout.split(/\r\n|\r|\n/);
|
||||||
for (let i = 0; i < outputs.length; i++) {
|
for (let i = 0; i < outputs.length; i++) {
|
||||||
const output = JSON.parse(outputs[i]);
|
const output = JSON.parse(outputs[i]);
|
||||||
handleOutputJSON(sub, sub_db, output, i === 0, multiUserMode)
|
handleOutputJSON(sub, sub_db, output, i === 0)
|
||||||
if (err.stderr.includes(output['id']) && archive_path) {
|
if (err.stderr.includes(output['id']) && archive_path) {
|
||||||
// we found a video that errored! add it to the archive to prevent future errors
|
// we found a video that errored! add it to the archive to prevent future errors
|
||||||
fs.appendFileSync(archive_path, output['id']);
|
fs.appendFileSync(archive_path, output['id']);
|
||||||
@@ -373,7 +315,7 @@ async function getVideosForSub(sub, user_uid = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const reset_videos = i === 0;
|
const reset_videos = i === 0;
|
||||||
handleOutputJSON(sub, sub_db, output_json, multiUserMode, reset_videos);
|
handleOutputJSON(sub, sub_db, output_json, reset_videos);
|
||||||
|
|
||||||
// TODO: Potentially store downloaded files in db?
|
// TODO: Potentially store downloaded files in db?
|
||||||
|
|
||||||
@@ -381,12 +323,10 @@ async function getVideosForSub(sub, user_uid = null) {
|
|||||||
resolve(true);
|
resolve(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, err => {
|
|
||||||
logger.error(err);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleOutputJSON(sub, sub_db, output_json, multiUserMode = null, reset_videos = false) {
|
function handleOutputJSON(sub, sub_db, output_json, reset_videos = false) {
|
||||||
if (sub.streamingOnly) {
|
if (sub.streamingOnly) {
|
||||||
if (reset_videos) {
|
if (reset_videos) {
|
||||||
sub_db.assign({videos: []}).write();
|
sub_db.assign({videos: []}).write();
|
||||||
@@ -397,9 +337,6 @@ function handleOutputJSON(sub, sub_db, output_json, multiUserMode = null, reset_
|
|||||||
|
|
||||||
// add to db
|
// add to db
|
||||||
sub_db.get('videos').push(output_json).write();
|
sub_db.get('videos').push(output_json).write();
|
||||||
} else {
|
|
||||||
// TODO: make multiUserMode obj
|
|
||||||
db_api.registerFileDB(path.basename(output_json['_filename']), sub.type, multiUserMode, sub);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
var fs = require('fs-extra')
|
|
||||||
var path = require('path')
|
|
||||||
const config_api = require('./config');
|
|
||||||
|
|
||||||
const is_windows = process.platform === 'win32';
|
|
||||||
|
|
||||||
function getTrueFileName(unfixed_path, type) {
|
|
||||||
let fixed_path = unfixed_path;
|
|
||||||
|
|
||||||
const new_ext = (type === 'audio' ? 'mp3' : 'mp4');
|
|
||||||
let unfixed_parts = unfixed_path.split('.');
|
|
||||||
const old_ext = unfixed_parts[unfixed_parts.length-1];
|
|
||||||
|
|
||||||
|
|
||||||
if (old_ext !== new_ext) {
|
|
||||||
unfixed_parts[unfixed_parts.length-1] = new_ext;
|
|
||||||
fixed_path = unfixed_parts.join('.');
|
|
||||||
}
|
|
||||||
return fixed_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getJSONMp4(name, customPath, openReadPerms = false) {
|
|
||||||
var obj = null; // output
|
|
||||||
if (!customPath) customPath = config_api.getConfigItem('ytdl_video_folder_path');
|
|
||||||
var jsonPath = path.join(customPath, name + ".info.json");
|
|
||||||
var alternateJsonPath = path.join(customPath, name + ".mp4.info.json");
|
|
||||||
if (fs.existsSync(jsonPath))
|
|
||||||
{
|
|
||||||
obj = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
|
||||||
} else if (fs.existsSync(alternateJsonPath)) {
|
|
||||||
obj = JSON.parse(fs.readFileSync(alternateJsonPath, 'utf8'));
|
|
||||||
}
|
|
||||||
else obj = 0;
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getJSONMp3(name, customPath, openReadPerms = false) {
|
|
||||||
var obj = null;
|
|
||||||
if (!customPath) customPath = config_api.getConfigItem('ytdl_audio_folder_path');
|
|
||||||
var jsonPath = path.join(customPath, name + ".info.json");
|
|
||||||
var alternateJsonPath = path.join(customPath, name + ".mp3.info.json");
|
|
||||||
if (fs.existsSync(jsonPath)) {
|
|
||||||
obj = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
|
||||||
}
|
|
||||||
else if (fs.existsSync(alternateJsonPath)) {
|
|
||||||
obj = JSON.parse(fs.readFileSync(alternateJsonPath, 'utf8'));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
obj = 0;
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fixVideoMetadataPerms(name, type, customPath = null) {
|
|
||||||
if (is_windows) return;
|
|
||||||
if (!customPath) customPath = type === 'audio' ? config_api.getConfigItem('ytdl_audio_folder_path')
|
|
||||||
: config_api.getConfigItem('ytdl_video_folder_path');
|
|
||||||
|
|
||||||
const ext = type === 'audio' ? '.mp3' : '.mp4';
|
|
||||||
|
|
||||||
const files_to_fix = [
|
|
||||||
// JSONs
|
|
||||||
path.join(customPath, name + '.info.json'),
|
|
||||||
path.join(customPath, name + ext + '.info.json'),
|
|
||||||
// Thumbnails
|
|
||||||
path.join(customPath, name + '.webp'),
|
|
||||||
path.join(customPath, name + '.jpg')
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const file of files_to_fix) {
|
|
||||||
if (!fs.existsSync(file)) continue;
|
|
||||||
fs.chmodSync(file, 0o644);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// objects
|
|
||||||
|
|
||||||
function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date) {
|
|
||||||
this.id = id;
|
|
||||||
this.title = title;
|
|
||||||
this.thumbnailURL = thumbnailURL;
|
|
||||||
this.isAudio = isAudio;
|
|
||||||
this.duration = duration;
|
|
||||||
this.url = url;
|
|
||||||
this.uploader = uploader;
|
|
||||||
this.size = size;
|
|
||||||
this.path = path;
|
|
||||||
this.upload_date = upload_date;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
getJSONMp3: getJSONMp3,
|
|
||||||
getJSONMp4: getJSONMp4,
|
|
||||||
getTrueFileName: getTrueFileName,
|
|
||||||
fixVideoMetadataPerms: fixVideoMetadataPerms,
|
|
||||||
File: File
|
|
||||||
}
|
|
||||||
BIN
backend/youtube-dl.exe
Normal file
BIN
backend/youtube-dl.exe
Normal file
Binary file not shown.
28
package-lock.json
generated
28
package-lock.json
generated
@@ -2557,16 +2557,6 @@
|
|||||||
"integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
|
"integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"bindings": {
|
|
||||||
"version": "1.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
|
||||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
|
||||||
"file-uri-to-path": "1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"blob": {
|
"blob": {
|
||||||
"version": "0.0.4",
|
"version": "0.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz",
|
||||||
@@ -5240,13 +5230,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz",
|
||||||
"integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw=="
|
"integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw=="
|
||||||
},
|
},
|
||||||
"file-uri-to-path": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"filename-regex": {
|
"filename-regex": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
|
||||||
@@ -7172,8 +7155,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"bindings": "^1.5.0",
|
|
||||||
"nan": "^2.12.1",
|
|
||||||
"node-pre-gyp": "*"
|
"node-pre-gyp": "*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -9096,11 +9077,6 @@
|
|||||||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
|
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"nan": {
|
|
||||||
"version": "2.14.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
|
|
||||||
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw=="
|
|
||||||
},
|
|
||||||
"nanomatch": {
|
"nanomatch": {
|
||||||
"version": "1.2.13",
|
"version": "1.2.13",
|
||||||
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
||||||
@@ -14146,8 +14122,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"bindings": "^1.5.0",
|
|
||||||
"nan": "^2.12.1",
|
|
||||||
"node-pre-gyp": "*"
|
"node-pre-gyp": "*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -15226,8 +15200,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"bindings": "^1.5.0",
|
|
||||||
"nan": "^2.12.1",
|
|
||||||
"node-pre-gyp": "*"
|
"node-pre-gyp": "*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.2",
|
||||||
"filesize": "^6.1.0",
|
"filesize": "^6.1.0",
|
||||||
"fingerprintjs2": "^2.1.0",
|
"fingerprintjs2": "^2.1.0",
|
||||||
"nan": "^2.14.1",
|
|
||||||
"ng-lazyload-image": "^7.0.1",
|
"ng-lazyload-image": "^7.0.1",
|
||||||
"ngx-file-drop": "^9.0.1",
|
"ngx-file-drop": "^9.0.1",
|
||||||
"ngx-videogular": "^9.0.1",
|
"ngx-videogular": "^9.0.1",
|
||||||
|
|||||||
@@ -71,8 +71,6 @@ import { AddUserDialogComponent } from './dialogs/add-user-dialog/add-user-dialo
|
|||||||
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';
|
import { CookiesUploaderDialogComponent } from './dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component';
|
||||||
import { LogsViewerComponent } from './components/logs-viewer/logs-viewer.component';
|
|
||||||
import { ModifyPlaylistComponent } from './dialogs/modify-playlist/modify-playlist.component';
|
|
||||||
|
|
||||||
registerLocaleData(es, 'es');
|
registerLocaleData(es, 'es');
|
||||||
|
|
||||||
@@ -111,9 +109,7 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
|||||||
AddUserDialogComponent,
|
AddUserDialogComponent,
|
||||||
ManageUserComponent,
|
ManageUserComponent,
|
||||||
ManageRoleComponent,
|
ManageRoleComponent,
|
||||||
CookiesUploaderDialogComponent,
|
CookiesUploaderDialogComponent
|
||||||
LogsViewerComponent,
|
|
||||||
ModifyPlaylistComponent
|
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
|||||||
@@ -14,9 +14,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<button style="top: 15px;" (click)="clearDownloads(session_downloads.key)" mat-stroked-button color="warn">Clear all downloads</button>
|
|
||||||
</div>
|
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export class DownloadsComponent implements OnInit, OnDestroy {
|
|||||||
valid_sessions_length = 0;
|
valid_sessions_length = 0;
|
||||||
|
|
||||||
sort_downloads = (a, b) => {
|
sort_downloads = (a, b) => {
|
||||||
const result = b.value.timestamp_start - a.value.timestamp_start;
|
const result = a.value.timestamp_start < b.value.timestamp_start;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ export class DownloadsComponent implements OnInit, OnDestroy {
|
|||||||
clearDownload(session_id, download_uid) {
|
clearDownload(session_id, download_uid) {
|
||||||
this.postsService.clearDownloads(false, session_id, download_uid).subscribe(res => {
|
this.postsService.clearDownloads(false, session_id, download_uid).subscribe(res => {
|
||||||
if (res['success']) {
|
if (res['success']) {
|
||||||
// this.downloads = res['downloads'];
|
this.downloads = res['downloads'];
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -107,32 +107,11 @@ export class DownloadsComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
assignNewValues(new_downloads_by_session) {
|
assignNewValues(new_downloads_by_session) {
|
||||||
const session_keys = Object.keys(new_downloads_by_session);
|
const session_keys = Object.keys(new_downloads_by_session);
|
||||||
|
|
||||||
// remove missing session IDs
|
|
||||||
const current_session_ids = Object.keys(this.downloads);
|
|
||||||
const missing_session_ids = current_session_ids.filter(session => session_keys.indexOf(session) === -1)
|
|
||||||
|
|
||||||
for (const missing_session_id of missing_session_ids) {
|
|
||||||
delete this.downloads[missing_session_id];
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop through sessions
|
|
||||||
for (let i = 0; i < session_keys.length; i++) {
|
for (let i = 0; i < session_keys.length; i++) {
|
||||||
const session_id = session_keys[i];
|
const session_id = session_keys[i];
|
||||||
const session_downloads_by_id = new_downloads_by_session[session_id];
|
const session_downloads_by_id = new_downloads_by_session[session_id];
|
||||||
const session_download_ids = Object.keys(session_downloads_by_id);
|
const session_download_ids = Object.keys(session_downloads_by_id);
|
||||||
|
|
||||||
if (this.downloads[session_id]) {
|
|
||||||
// remove missing download IDs
|
|
||||||
const current_download_ids = Object.keys(this.downloads[session_id]);
|
|
||||||
const missing_download_ids = current_download_ids.filter(download => session_download_ids.indexOf(download) === -1)
|
|
||||||
|
|
||||||
for (const missing_download_id of missing_download_ids) {
|
|
||||||
console.log('removing missing download id');
|
|
||||||
delete this.downloads[session_id][missing_download_id];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.downloads[session_id]) {
|
if (!this.downloads[session_id]) {
|
||||||
this.downloads[session_id] = session_downloads_by_id;
|
this.downloads[session_id] = session_downloads_by_id;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -49,19 +49,9 @@ export class LoginComponent implements OnInit {
|
|||||||
this.loggingIn = false;
|
this.loggingIn = false;
|
||||||
if (res['token']) {
|
if (res['token']) {
|
||||||
this.postsService.afterLogin(res['user'], res['token'], res['permissions'], res['available_permissions']);
|
this.postsService.afterLogin(res['user'], res['token'], res['permissions'], res['available_permissions']);
|
||||||
} else {
|
|
||||||
this.openSnackBar('Login failed, unknown error.');
|
|
||||||
}
|
}
|
||||||
}, err => {
|
}, err => {
|
||||||
this.loggingIn = false;
|
this.loggingIn = false;
|
||||||
const error_code = err.status;
|
|
||||||
if (error_code === 401) {
|
|
||||||
this.openSnackBar('User name or password is incorrect!');
|
|
||||||
} else if (error_code === 404) {
|
|
||||||
this.openSnackBar('Login failed, cannot connect to the server.');
|
|
||||||
} else {
|
|
||||||
this.openSnackBar('Login failed, unknown error.');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +84,7 @@ export class LoginComponent implements OnInit {
|
|||||||
this.loginUsernameInput = res['user']['name'];
|
this.loginUsernameInput = res['user']['name'];
|
||||||
this.selectedTabIndex = 0;
|
this.selectedTabIndex = 0;
|
||||||
} else {
|
} else {
|
||||||
this.openSnackBar('Failed to register user, unknown error.');
|
|
||||||
}
|
}
|
||||||
}, err => {
|
}, err => {
|
||||||
this.registering = false;
|
this.registering = false;
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
<div style="height: 100%">
|
|
||||||
<div *ngIf="logs_loading" style="position: absolute; top: 40%; left: 50%">
|
|
||||||
<mat-spinner [diameter]="32"></mat-spinner>
|
|
||||||
</div>
|
|
||||||
<textarea style="height: 275px" matInput readonly [(ngModel)]="logs" placeholder="Logs will appear here" i18n-placeholder="Logs placeholder"></textarea>
|
|
||||||
<div>
|
|
||||||
<button style="margin-top: 12px;" [cdkCopyToClipboard]="logs" (click)="copiedLogsToClipboard()" mat-flat-button color="primary"><ng-container i18n="Copy to clipboard button text">Copy to clipboard</ng-container></button>
|
|
||||||
<div style="float: right">
|
|
||||||
<ng-container i18n="Label for lines select in logger view">Lines:</ng-container>
|
|
||||||
<mat-form-field style="width: 75px;">
|
|
||||||
<mat-select (selectionChange)="getLogs()" [(ngModel)]="requested_lines">
|
|
||||||
<mat-option [value]="10">10</mat-option>
|
|
||||||
<mat-option [value]="25">25</mat-option>
|
|
||||||
<mat-option [value]="50">50</mat-option>
|
|
||||||
<mat-option [value]="100">100</mat-option>
|
|
||||||
<mat-option [value]="0">All</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { LogsViewerComponent } from './logs-viewer.component';
|
|
||||||
|
|
||||||
describe('LogsViewerComponent', () => {
|
|
||||||
let component: LogsViewerComponent;
|
|
||||||
let fixture: ComponentFixture<LogsViewerComponent>;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
declarations: [ LogsViewerComponent ]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(LogsViewerComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { Component, OnInit, AfterViewInit } from '@angular/core';
|
|
||||||
import { PostsService } from '../../posts.services';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-logs-viewer',
|
|
||||||
templateUrl: './logs-viewer.component.html',
|
|
||||||
styleUrls: ['./logs-viewer.component.scss']
|
|
||||||
})
|
|
||||||
export class LogsViewerComponent implements OnInit {
|
|
||||||
|
|
||||||
logs: string = null;
|
|
||||||
requested_lines = 50;
|
|
||||||
logs_loading = false;
|
|
||||||
constructor(private postsService: PostsService) { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.getLogs();
|
|
||||||
}
|
|
||||||
|
|
||||||
getLogs() {
|
|
||||||
if (!this.logs) { this.logs_loading = true; } // only show loading spinner at the first load
|
|
||||||
this.postsService.getLogs(this.requested_lines !== 0 ? this.requested_lines : null).subscribe(res => {
|
|
||||||
this.logs_loading = false;
|
|
||||||
if (res['logs']) {
|
|
||||||
this.logs = res['logs'];
|
|
||||||
} else {
|
|
||||||
this.postsService.openSnackBar('Failed to retrieve logs!');
|
|
||||||
}
|
|
||||||
}, err => {
|
|
||||||
this.logs_loading = false;
|
|
||||||
console.error(err);
|
|
||||||
this.postsService.openSnackBar('Failed to retrieve logs!');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
copiedLogsToClipboard() {
|
|
||||||
this.postsService.openSnackBar('Logs copied to clipboard!');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<h4 mat-dialog-title><ng-container i18n="Modify playlist dialog title">Modify playlist</ng-container></h4>
|
|
||||||
|
|
||||||
<mat-dialog-content>
|
|
||||||
<!-- Playlist info -->
|
|
||||||
<div>
|
|
||||||
<mat-form-field color="accent">
|
|
||||||
<input matInput placeholder="Name" i18n-placeholder="Name" [(ngModel)]="playlist.name">
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Playlist order -->
|
|
||||||
<mat-button-toggle-group class="media-list" cdkDropList (cdkDropListDropped)="drop($event)" style="width: 80%; left: 9%" vertical name="videoSelect" aria-label="Video Select" #group="matButtonToggleGroup">
|
|
||||||
<mat-button-toggle class="media-box" cdkDrag *ngFor="let playlist_item of playlist.fileNames; let i = index" [checked]="false"><div><div class="playlist-item-text">{{playlist_item}}</div> <button (click)="removeContent(i)" class="remove-item-button" mat-icon-button><mat-icon>cancel</mat-icon></button></div></mat-button-toggle>
|
|
||||||
</mat-button-toggle-group>
|
|
||||||
|
|
||||||
<div class="add-content-button">
|
|
||||||
<button [disabled]="available_files.length === 0" mat-stroked-button [matMenuTriggerFor]="menu">Add more content</button>
|
|
||||||
</div>
|
|
||||||
<mat-menu #menu="matMenu">
|
|
||||||
<button *ngFor="let file of available_files" (click)="addContent(file)" mat-menu-item>{{file}}</button>
|
|
||||||
</mat-menu>
|
|
||||||
|
|
||||||
</mat-dialog-content>
|
|
||||||
|
|
||||||
<mat-dialog-actions>
|
|
||||||
<!-- Save -->
|
|
||||||
<button [disabled]="!playlistChanged()" (click)="updatePlaylist()" [disabled]="playlistChanged()" mat-raised-button color="accent">Save</button>
|
|
||||||
</mat-dialog-actions>
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
.media-list {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-box {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.cdk-drag-preview {
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
|
|
||||||
0 8px 10px 1px rgba(0, 0, 0, 0.14),
|
|
||||||
0 3px 14px 2px rgba(0, 0, 0, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cdk-drag-placeholder {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cdk-drag-animating {
|
|
||||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-box:last-child {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-list.cdk-drop-list-dragging .media-box:not(.cdk-drag-placeholder) {
|
|
||||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-content-button {
|
|
||||||
margin-top: 15px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-item-button {
|
|
||||||
right: 10px;
|
|
||||||
position: absolute;
|
|
||||||
top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playlist-item-text {
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
max-width: 70%;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { ModifyPlaylistComponent } from './modify-playlist.component';
|
|
||||||
|
|
||||||
describe('ModifyPlaylistComponent', () => {
|
|
||||||
let component: ModifyPlaylistComponent;
|
|
||||||
let fixture: ComponentFixture<ModifyPlaylistComponent>;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
declarations: [ ModifyPlaylistComponent ]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(ModifyPlaylistComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
import { Component, OnInit, Inject } from '@angular/core';
|
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
|
||||||
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
|
|
||||||
import { PostsService } from 'app/posts.services';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-modify-playlist',
|
|
||||||
templateUrl: './modify-playlist.component.html',
|
|
||||||
styleUrls: ['./modify-playlist.component.scss']
|
|
||||||
})
|
|
||||||
export class ModifyPlaylistComponent implements OnInit {
|
|
||||||
|
|
||||||
original_playlist = null;
|
|
||||||
playlist = null;
|
|
||||||
available_files = [];
|
|
||||||
all_files = [];
|
|
||||||
playlist_updated = false;
|
|
||||||
|
|
||||||
constructor(@Inject(MAT_DIALOG_DATA) public data: any,
|
|
||||||
private postsService: PostsService,
|
|
||||||
public dialogRef: MatDialogRef<ModifyPlaylistComponent>) { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
if (this.data) {
|
|
||||||
this.playlist = JSON.parse(JSON.stringify(this.data.playlist));
|
|
||||||
this.original_playlist = JSON.parse(JSON.stringify(this.data.playlist));
|
|
||||||
this.getFiles();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getFiles() {
|
|
||||||
if (this.playlist.type === 'audio') {
|
|
||||||
this.postsService.getMp3s().subscribe(res => {
|
|
||||||
this.processFiles(res['mp3s']);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.postsService.getMp4s().subscribe(res => {
|
|
||||||
this.processFiles(res['mp4s']);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
processFiles(new_files = null) {
|
|
||||||
if (new_files) { this.all_files = new_files.map(file => file.id); }
|
|
||||||
this.available_files = this.all_files.filter(e => !this.playlist.fileNames.includes(e))
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePlaylist() {
|
|
||||||
this.postsService.updatePlaylist(this.playlist).subscribe(res => {
|
|
||||||
this.playlist_updated = true;
|
|
||||||
this.postsService.openSnackBar('Playlist updated successfully.');
|
|
||||||
this.getPlaylist();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
playlistChanged() {
|
|
||||||
return JSON.stringify(this.playlist) === JSON.stringify(this.original_playlist);
|
|
||||||
}
|
|
||||||
|
|
||||||
getPlaylist() {
|
|
||||||
this.postsService.getPlaylist(this.playlist.id, this.playlist.type, null).subscribe(res => {
|
|
||||||
if (res['playlist']) {
|
|
||||||
this.playlist = res['playlist'];
|
|
||||||
this.original_playlist = JSON.parse(JSON.stringify(this.playlist));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addContent(file) {
|
|
||||||
this.playlist.fileNames.push(file);
|
|
||||||
this.processFiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeContent(index) {
|
|
||||||
this.playlist.fileNames.splice(index, 1);
|
|
||||||
this.processFiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
drop(event: CdkDragDrop<string[]>) {
|
|
||||||
moveItemInArray(this.playlist.fileNames, event.previousIndex, event.currentIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -3,20 +3,16 @@
|
|||||||
<mat-dialog-content>
|
<mat-dialog-content>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 mb-4">
|
<div class="col-12">
|
||||||
<mat-form-field color="accent">
|
<mat-form-field color="accent">
|
||||||
<input [(ngModel)]="url" matInput placeholder="URL" i18n-placeholder="Subscription URL input placeholder" required aria-required="true">
|
<input [(ngModel)]="url" matInput placeholder="URL" i18n-placeholder="Subscription URL input placeholder" required aria-required="true">
|
||||||
<mat-hint><ng-container i18n="Subscription URL input hint">The playlist or channel URL</ng-container></mat-hint>
|
<mat-hint><ng-container i18n="Subscription URL input hint">The playlist or channel URL</ng-container></mat-hint>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<mat-divider></mat-divider>
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<mat-form-field color="accent">
|
<mat-form-field color="accent">
|
||||||
<input [(ngModel)]="name" matInput placeholder="Custom name" i18n-placeholder="Subscription custom name placeholder">
|
<input [(ngModel)]="name" matInput placeholder="Custom name" i18n-placeholder="Subscription custom name placeholder">
|
||||||
|
<mat-hint><ng-container i18n="Custom name input hint">This is optional</ng-container></mat-hint>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 mt-3">
|
<div class="col-12 mt-3">
|
||||||
@@ -35,33 +31,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div>
|
<div>
|
||||||
<mat-checkbox [(ngModel)]="audioOnlyMode"><ng-container i18n="Streaming-only mode">Audio-only mode</ng-container></mat-checkbox>
|
<mat-checkbox [(ngModel)]="streamingOnlyMode"><ng-container i18n="Streaming-only mode">Streaming-only mode</ng-container></mat-checkbox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
|
||||||
<div>
|
|
||||||
<mat-checkbox [disabled]="audioOnlyMode" [(ngModel)]="streamingOnlyMode"><ng-container i18n="Streaming-only mode">Streaming-only mode</ng-container></mat-checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 mb-3">
|
|
||||||
<mat-form-field color="accent">
|
|
||||||
<input [(ngModel)]="customArgs" matInput placeholder="Custom args" i18n-placeholder="Subscription custom args placeholder">
|
|
||||||
<button class="args-edit-button" (click)="openArgsModifierDialog()" mat-icon-button><mat-icon>edit</mat-icon></button>
|
|
||||||
<mat-hint>
|
|
||||||
<ng-container i18n="Custom args hint">These are added after the standard args.</ng-container>
|
|
||||||
</mat-hint>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<mat-form-field color="accent">
|
|
||||||
<input [(ngModel)]="customFileOutput" matInput placeholder="Custom file output" i18n-placeholder="Subscription custom file output placeholder">
|
|
||||||
<mat-hint>
|
|
||||||
<a target="_blank" href="https://github.com/ytdl-org/youtube-dl/blob/master/README.md#output-template">
|
|
||||||
<ng-container i18n="Custom output template documentation link">Documentation</ng-container></a>.
|
|
||||||
<ng-container i18n="Custom Output input hint">Path is relative to the config download path. Don't include extension.</ng-container>
|
|
||||||
</mat-hint>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</mat-dialog-content>
|
</mat-dialog-content>
|
||||||
|
|||||||
@@ -6,8 +6,3 @@
|
|||||||
.mat-spinner {
|
.mat-spinner {
|
||||||
margin-left: 5%;
|
margin-left: 5%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.args-edit-button {
|
|
||||||
position: absolute;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { MatDialogRef, MatDialog } from '@angular/material/dialog';
|
import { MatDialogRef } from '@angular/material/dialog';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { PostsService } from 'app/posts.services';
|
import { PostsService } from 'app/posts.services';
|
||||||
import { ArgModifierDialogComponent } from '../arg-modifier-dialog/arg-modifier-dialog.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-subscribe-dialog',
|
selector: 'app-subscribe-dialog',
|
||||||
@@ -23,12 +22,6 @@ export class SubscribeDialogComponent implements OnInit {
|
|||||||
// no videos actually downloaded, just streamed
|
// no videos actually downloaded, just streamed
|
||||||
streamingOnlyMode = false;
|
streamingOnlyMode = false;
|
||||||
|
|
||||||
// audio only mode
|
|
||||||
audioOnlyMode = false;
|
|
||||||
|
|
||||||
customFileOutput = '';
|
|
||||||
customArgs = '';
|
|
||||||
|
|
||||||
time_units = [
|
time_units = [
|
||||||
'day',
|
'day',
|
||||||
'week',
|
'week',
|
||||||
@@ -38,7 +31,6 @@ export class SubscribeDialogComponent implements OnInit {
|
|||||||
|
|
||||||
constructor(private postsService: PostsService,
|
constructor(private postsService: PostsService,
|
||||||
private snackBar: MatSnackBar,
|
private snackBar: MatSnackBar,
|
||||||
private dialog: MatDialog,
|
|
||||||
public dialogRef: MatDialogRef<SubscribeDialogComponent>) { }
|
public dialogRef: MatDialogRef<SubscribeDialogComponent>) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@@ -57,8 +49,7 @@ export class SubscribeDialogComponent implements OnInit {
|
|||||||
if (!this.download_all) {
|
if (!this.download_all) {
|
||||||
timerange = 'now-' + this.timerange_amount.toString() + this.timerange_unit;
|
timerange = 'now-' + this.timerange_amount.toString() + this.timerange_unit;
|
||||||
}
|
}
|
||||||
this.postsService.createSubscription(this.url, this.name, timerange, this.streamingOnlyMode,
|
this.postsService.createSubscription(this.url, this.name, timerange, this.streamingOnlyMode).subscribe(res => {
|
||||||
this.audioOnlyMode, this.customArgs, this.customFileOutput).subscribe(res => {
|
|
||||||
this.subscribing = false;
|
this.subscribing = false;
|
||||||
if (res['new_sub']) {
|
if (res['new_sub']) {
|
||||||
this.dialogRef.close(res['new_sub']);
|
this.dialogRef.close(res['new_sub']);
|
||||||
@@ -72,20 +63,6 @@ export class SubscribeDialogComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// modify custom args
|
|
||||||
openArgsModifierDialog() {
|
|
||||||
const dialogRef = this.dialog.open(ArgModifierDialogComponent, {
|
|
||||||
data: {
|
|
||||||
initial_args: this.customArgs
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dialogRef.afterClosed().subscribe(new_args => {
|
|
||||||
if (new_args !== null && new_args !== undefined) {
|
|
||||||
this.customArgs = new_args;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public openSnackBar(message: string, action = '') {
|
public openSnackBar(message: string, action = '') {
|
||||||
this.snackBar.open(message, action, {
|
this.snackBar.open(message, action, {
|
||||||
duration: 2000,
|
duration: 2000,
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
<div style="padding:5px">
|
<div style="padding:5px">
|
||||||
<div style="height: 52px;">
|
<div style="height: 52px;">
|
||||||
<div>
|
<div>
|
||||||
<b><a class="file-link" href="javascript:void(0)" (click)="!playlist ? mainComponent.goToFile(name, isAudio, uid) : mainComponent.goToPlaylist(name, type)">{{title}}</a></b>
|
<b><a class="file-link" href="javascript:void(0)" (click)="!isPlaylist ? mainComponent.goToFile(name, isAudio, uid) : mainComponent.goToPlaylist(name, type)">{{title}}</a></b>
|
||||||
</div>
|
</div>
|
||||||
<span class="max-two-lines"><ng-container i18n="File or playlist ID">ID:</ng-container> {{name}}</span>
|
<span class="max-two-lines"><ng-container i18n="File or playlist ID">ID:</ng-container> {{name}}</span>
|
||||||
<div *ngIf="playlist"><ng-container i18n="Playlist video count">Count:</ng-container> {{count}}</div>
|
<div *ngIf="isPlaylist"><ng-container i18n="Playlist video count">Count:</ng-container> {{count}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!image_errored && thumbnailURL" class="img-div">
|
<div *ngIf="!image_errored && thumbnailURL" class="img-div">
|
||||||
<img class="image" (error) ="onImgError($event)" [id]="type" [lazyLoad]="thumbnailURL" [customObservable]="scrollAndLoad" (onLoad)="imageLoaded($event)" alt="Thumbnail">
|
<img class="image" (error) ="onImgError($event)" [id]="type" [lazyLoad]="thumbnailURL" [customObservable]="scrollAndLoad" (onLoad)="imageLoaded($event)" alt="Thumbnail">
|
||||||
@@ -14,16 +14,11 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button [matMenuTriggerFor]="playlist_menu" *ngIf="playlist" class="deleteButton" mat-icon-button><mat-icon>more_vert</mat-icon></button>
|
<button *ngIf="isPlaylist" (click)="deleteFile()" class="deleteButton" mat-icon-button><mat-icon>delete_forever</mat-icon></button>
|
||||||
<mat-menu #playlist_menu="matMenu">
|
<button [matMenuTriggerFor]="action_menu" *ngIf="!isPlaylist" class="deleteButton" mat-icon-button><mat-icon>more_vert</mat-icon></button>
|
||||||
<button (click)="editPlaylistDialog()" mat-menu-item><mat-icon>edit</mat-icon><ng-container i18n="Playlist edit button">Edit</ng-container></button>
|
|
||||||
<button (click)="deleteFile()" mat-menu-item><mat-icon>delete_forever</mat-icon><ng-container i18n="Delete playlist">Delete</ng-container></button>
|
|
||||||
</mat-menu>
|
|
||||||
<button [matMenuTriggerFor]="action_menu" *ngIf="!playlist" class="deleteButton" mat-icon-button><mat-icon>more_vert</mat-icon></button>
|
|
||||||
<mat-menu #action_menu="matMenu">
|
<mat-menu #action_menu="matMenu">
|
||||||
<button (click)="openVideoInfoDialog()" mat-menu-item><mat-icon>info</mat-icon><ng-container i18n="Video info button">Info</ng-container></button>
|
<button (click)="openVideoInfoDialog()" mat-menu-item><mat-icon>info</mat-icon><ng-container i18n="Video info button">Info</ng-container></button>
|
||||||
<button (click)="deleteFile()" mat-menu-item><mat-icon>delete</mat-icon><ng-container i18n="Delete video button">Delete</ng-container></button>
|
<button (click)="deleteFile()" mat-menu-item><mat-icon>delete</mat-icon><ng-container i18n="Delete video button">Delete</ng-container></button>
|
||||||
<button *ngIf="use_youtubedl_archive" (click)="deleteFile(true)" mat-menu-item><mat-icon>delete_forever</mat-icon><ng-container i18n="Delete and blacklist video button">Delete and blacklist</ng-container></button>
|
<button *ngIf="use_youtubedl_archive" (click)="deleteFile(true)" mat-menu-item><mat-icon>delete_forever</mat-icon><ng-container i18n="Delete and blacklist video button">Delete and blacklist</ng-container></button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { Subject, Observable } from 'rxjs';
|
|||||||
import 'rxjs/add/observable/merge';
|
import 'rxjs/add/observable/merge';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { VideoInfoDialogComponent } from 'app/dialogs/video-info-dialog/video-info-dialog.component';
|
import { VideoInfoDialogComponent } from 'app/dialogs/video-info-dialog/video-info-dialog.component';
|
||||||
import { ModifyPlaylistComponent } from '../dialogs/modify-playlist/modify-playlist.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-file-card',
|
selector: 'app-file-card',
|
||||||
@@ -23,7 +22,7 @@ export class FileCardComponent implements OnInit {
|
|||||||
@Input() thumbnailURL: string;
|
@Input() thumbnailURL: string;
|
||||||
@Input() isAudio = true;
|
@Input() isAudio = true;
|
||||||
@Output() removeFile: EventEmitter<string> = new EventEmitter<string>();
|
@Output() removeFile: EventEmitter<string> = new EventEmitter<string>();
|
||||||
@Input() playlist = null;
|
@Input() isPlaylist = false;
|
||||||
@Input() count = null;
|
@Input() count = null;
|
||||||
@Input() use_youtubedl_archive = false;
|
@Input() use_youtubedl_archive = false;
|
||||||
type;
|
type;
|
||||||
@@ -47,15 +46,15 @@ export class FileCardComponent implements OnInit {
|
|||||||
this.type = this.isAudio ? 'audio' : 'video';
|
this.type = this.isAudio ? 'audio' : 'video';
|
||||||
|
|
||||||
if (this.file && this.file.url && this.file.url.includes('youtu')) {
|
if (this.file && this.file.url && this.file.url.includes('youtu')) {
|
||||||
const string_id = (this.playlist ? '?list=' : '?v=')
|
const string_id = (this.isPlaylist ? '?list=' : '?v=')
|
||||||
const index_offset = (this.playlist ? 6 : 3);
|
const index_offset = (this.isPlaylist ? 6 : 3);
|
||||||
const end_index = this.file.url.indexOf(string_id) + index_offset;
|
const end_index = this.file.url.indexOf(string_id) + index_offset;
|
||||||
this.name = this.file.url.substring(end_index, this.file.url.length);
|
this.name = this.file.url.substring(end_index, this.file.url.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteFile(blacklistMode = false) {
|
deleteFile(blacklistMode = false) {
|
||||||
if (!this.playlist) {
|
if (!this.isPlaylist) {
|
||||||
this.postsService.deleteFile(this.uid, this.isAudio, blacklistMode).subscribe(result => {
|
this.postsService.deleteFile(this.uid, this.isAudio, blacklistMode).subscribe(result => {
|
||||||
if (result) {
|
if (result) {
|
||||||
this.openSnackBar('Delete success!', 'OK.');
|
this.openSnackBar('Delete success!', 'OK.');
|
||||||
@@ -81,24 +80,6 @@ export class FileCardComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
editPlaylistDialog() {
|
|
||||||
const dialogRef = this.dialog.open(ModifyPlaylistComponent, {
|
|
||||||
data: {
|
|
||||||
playlist: this.playlist,
|
|
||||||
width: '65vw'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe(res => {
|
|
||||||
// updates playlist in file manager if it changed
|
|
||||||
if (dialogRef.componentInstance.playlist_updated) {
|
|
||||||
this.playlist = dialogRef.componentInstance.original_playlist;
|
|
||||||
this.title = this.playlist.name;
|
|
||||||
this.count = this.playlist.fileNames.length;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onImgError(event) {
|
onImgError(event) {
|
||||||
this.image_errored = true;
|
this.image_errored = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,10 @@
|
|||||||
<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" #urlinput>
|
<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>
|
||||||
|
<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>
|
||||||
@@ -24,7 +27,7 @@
|
|||||||
</mat-label>
|
</mat-label>
|
||||||
<mat-select [ngModelOptions]="{standalone: true}" [(ngModel)]="selectedQuality">
|
<mat-select [ngModelOptions]="{standalone: true}" [(ngModel)]="selectedQuality">
|
||||||
<ng-container *ngFor="let option of qualityOptions[(audioOnly) ? 'audio' : 'video']">
|
<ng-container *ngFor="let option of qualityOptions[(audioOnly) ? 'audio' : 'video']">
|
||||||
<mat-option *ngIf="option.value === '' || url && cachedAvailableFormats[url] && cachedAvailableFormats[url]['formats'] && cachedAvailableFormats[url]['formats'][(audioOnly) ? 'audio' : 'video'][option.value]" [value]="option.value">
|
<mat-option *ngIf="option.value === '' || url && cachedAvailableFormats[url] && cachedAvailableFormats[url]['formats'] && cachedAvailableFormats[url]['formats'][(audioOnly) ? 'audio' : 'video'][option.note]" [value]="option.note">
|
||||||
{{option.label}}
|
{{option.label}}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -213,7 +216,7 @@
|
|||||||
<mat-grid-list *ngIf="playlists.audio.length > 0" (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
<mat-grid-list *ngIf="playlists.audio.length > 0" (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
||||||
<mat-grid-tile *ngFor="let playlist of playlists.audio; let i = index;">
|
<mat-grid-tile *ngFor="let playlist of playlists.audio; let i = index;">
|
||||||
<app-file-card #audiofilecard (removeFile)="removePlaylistMp3(playlist.id, i)" [title]="playlist.name" [name]="playlist.id" [thumbnailURL]="playlist_thumbnails[playlist.id]"
|
<app-file-card #audiofilecard (removeFile)="removePlaylistMp3(playlist.id, i)" [title]="playlist.name" [name]="playlist.id" [thumbnailURL]="playlist_thumbnails[playlist.id]"
|
||||||
[length]="null" [isAudio]="true" [playlist]="playlist" [count]="playlist.fileNames.length" [use_youtubedl_archive]="use_youtubedl_archive"></app-file-card>
|
[length]="null" [isAudio]="true" [isPlaylist]="true" [count]="playlist.fileNames.length" [use_youtubedl_archive]="use_youtubedl_archive"></app-file-card>
|
||||||
<mat-progress-bar *ngIf="downloading_content['audio'][playlist.id]" class="download-progress-bar" mode="indeterminate"></mat-progress-bar>
|
<mat-progress-bar *ngIf="downloading_content['audio'][playlist.id]" class="download-progress-bar" mode="indeterminate"></mat-progress-bar>
|
||||||
</mat-grid-tile>
|
</mat-grid-tile>
|
||||||
</mat-grid-list>
|
</mat-grid-list>
|
||||||
@@ -255,7 +258,7 @@
|
|||||||
<mat-grid-list *ngIf="playlists.video.length > 0" (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
<mat-grid-list *ngIf="playlists.video.length > 0" (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
||||||
<mat-grid-tile *ngFor="let playlist of playlists.video; let i = index;">
|
<mat-grid-tile *ngFor="let playlist of playlists.video; let i = index;">
|
||||||
<app-file-card #videofilecard (removeFile)="removePlaylistMp4(playlist.id, i)" [title]="playlist.name" [name]="playlist.id" [thumbnailURL]="playlist_thumbnails[playlist.id]"
|
<app-file-card #videofilecard (removeFile)="removePlaylistMp4(playlist.id, i)" [title]="playlist.name" [name]="playlist.id" [thumbnailURL]="playlist_thumbnails[playlist.id]"
|
||||||
[length]="null" [isAudio]="false" [playlist]="playlist" [count]="playlist.fileNames.length" [use_youtubedl_archive]="use_youtubedl_archive"></app-file-card>
|
[length]="null" [isAudio]="false" [isPlaylist]="true" [count]="playlist.fileNames.length" [use_youtubedl_archive]="use_youtubedl_archive"></app-file-card>
|
||||||
<mat-progress-bar *ngIf="downloading_content['video'][playlist.id]" class="download-progress-bar" mode="indeterminate"></mat-progress-bar>
|
<mat-progress-bar *ngIf="downloading_content['video'][playlist.id]" class="download-progress-bar" mode="indeterminate"></mat-progress-bar>
|
||||||
</mat-grid-tile>
|
</mat-grid-tile>
|
||||||
</mat-grid-list>
|
</mat-grid-list>
|
||||||
|
|||||||
@@ -108,47 +108,56 @@ export class MainComponent implements OnInit {
|
|||||||
{
|
{
|
||||||
'resolution': null,
|
'resolution': null,
|
||||||
'value': '',
|
'value': '',
|
||||||
'label': 'Max'
|
'label': 'Max',
|
||||||
|
'note': ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'resolution': '3840x2160',
|
'resolution': '3840x2160',
|
||||||
'value': '2160',
|
'value': '2160',
|
||||||
'label': '2160p (4K)'
|
'label': '2160p (4K)',
|
||||||
|
'note': '2160p'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'resolution': '2560x1440',
|
'resolution': '2560x1440',
|
||||||
'value': '1440',
|
'value': '1440',
|
||||||
'label': '1440p'
|
'label': '1440p',
|
||||||
|
'note': '1440p'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'resolution': '1920x1080',
|
'resolution': '1920x1080',
|
||||||
'value': '1080',
|
'value': '1080',
|
||||||
'label': '1080p'
|
'label': '1080p',
|
||||||
|
'note': '1080p'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'resolution': '1280x720',
|
'resolution': '1280x720',
|
||||||
'value': '720',
|
'value': '720',
|
||||||
'label': '720p'
|
'label': '720p',
|
||||||
|
'note': '720p'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'resolution': '720x480',
|
'resolution': '720x480',
|
||||||
'value': '480',
|
'value': '480',
|
||||||
'label': '480p'
|
'label': '480p',
|
||||||
|
'note': '480p'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'resolution': '480x360',
|
'resolution': '480x360',
|
||||||
'value': '360',
|
'value': '360',
|
||||||
'label': '360p'
|
'label': '360p',
|
||||||
|
'note': '360p'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'resolution': '360x240',
|
'resolution': '360x240',
|
||||||
'value': '240',
|
'value': '240',
|
||||||
'label': '240p'
|
'label': '240p',
|
||||||
|
'note': '240p'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'resolution': '256x144',
|
'resolution': '256x144',
|
||||||
'value': '144',
|
'value': '144',
|
||||||
'label': '144p'
|
'label': '144p',
|
||||||
|
'note': '144p'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'audio': [
|
'audio': [
|
||||||
@@ -439,11 +448,10 @@ export class MainComponent implements OnInit {
|
|||||||
|
|
||||||
public removeFromMp3(name: string) {
|
public removeFromMp3(name: string) {
|
||||||
for (let i = 0; i < this.mp3s.length; i++) {
|
for (let i = 0; i < this.mp3s.length; i++) {
|
||||||
if (this.mp3s[i].id === name || this.mp3s[i].id + '.mp3' === name) {
|
if (this.mp3s[i].id === name) {
|
||||||
this.mp3s.splice(i, 1);
|
this.mp3s.splice(i, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.getMp3s();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public removePlaylistMp3(playlistID, index) {
|
public removePlaylistMp3(playlistID, index) {
|
||||||
@@ -458,11 +466,10 @@ export class MainComponent implements OnInit {
|
|||||||
|
|
||||||
public removeFromMp4(name: string) {
|
public removeFromMp4(name: string) {
|
||||||
for (let i = 0; i < this.mp4s.length; i++) {
|
for (let i = 0; i < this.mp4s.length; i++) {
|
||||||
if (this.mp4s[i].id === name || this.mp4s[i].id + '.mp4' === name) {
|
if (this.mp4s[i].id === name) {
|
||||||
this.mp4s.splice(i, 1);
|
this.mp4s.splice(i, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.getMp4s();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public removePlaylistMp4(playlistID, index) {
|
public removePlaylistMp4(playlistID, index) {
|
||||||
@@ -1034,14 +1041,16 @@ export class MainComponent implements OnInit {
|
|||||||
} else if (format_obj.type === 'video') {
|
} else if (format_obj.type === 'video') {
|
||||||
// check if video format is mp4
|
// check if video format is mp4
|
||||||
const key = format.height.toString();
|
const key = format.height.toString();
|
||||||
if (format.ext === 'mp4') {
|
if (true) {
|
||||||
format_obj['height'] = format.height;
|
format_obj['height'] = format.height;
|
||||||
format_obj['acodec'] = format.acodec;
|
format_obj['acodec'] = format.acodec;
|
||||||
format_obj['format_id'] = format.format_id;
|
format_obj['format_id'] = format.format_id;
|
||||||
|
format_obj['ext'] = format.ext;
|
||||||
|
format_obj['note'] = format.format_note;
|
||||||
|
|
||||||
// no acodec means no overwrite
|
// no acodec means no overwrite
|
||||||
if (!(video_formats[key]) || format_obj['acodec'] !== 'none') {
|
if (!(video_formats[key]) || format_obj['acodec'] !== 'none') {
|
||||||
video_formats[key] = format_obj;
|
video_formats[format_obj['note']] = format_obj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,8 +62,6 @@ export class PlayerComponent implements OnInit {
|
|||||||
|
|
||||||
downloading = false;
|
downloading = false;
|
||||||
|
|
||||||
original_volume = null;
|
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
onResize(event) {
|
onResize(event) {
|
||||||
this.innerWidth = window.innerWidth;
|
this.innerWidth = window.innerWidth;
|
||||||
@@ -129,7 +127,7 @@ export class PlayerComponent implements OnInit {
|
|||||||
this.currentItem = this.playlist[0];
|
this.currentItem = this.playlist[0];
|
||||||
this.currentIndex = 0;
|
this.currentIndex = 0;
|
||||||
this.show_player = true;
|
this.show_player = true;
|
||||||
} else if (this.subscriptionName || this.fileNames) {
|
} else if (this.type === 'subscription' || this.fileNames) {
|
||||||
this.show_player = true;
|
this.show_player = true;
|
||||||
this.parseFileNames();
|
this.parseFileNames();
|
||||||
}
|
}
|
||||||
@@ -183,6 +181,9 @@ export class PlayerComponent implements OnInit {
|
|||||||
fileType = 'audio/mp3';
|
fileType = 'audio/mp3';
|
||||||
} else if (this.type === 'video') {
|
} else if (this.type === 'video') {
|
||||||
fileType = 'video/mp4';
|
fileType = 'video/mp4';
|
||||||
|
} else if (this.type === 'subscription') {
|
||||||
|
// only supports mp4 for now
|
||||||
|
fileType = 'video/mp4';
|
||||||
} else {
|
} else {
|
||||||
// error
|
// error
|
||||||
console.error('Must have valid file type! Use \'audio\', \'video\', or \'subscription\'.');
|
console.error('Must have valid file type! Use \'audio\', \'video\', or \'subscription\'.');
|
||||||
@@ -197,7 +198,7 @@ export class PlayerComponent implements OnInit {
|
|||||||
fullLocation = this.baseStreamPath + baseLocation + encodeURIComponent(fileName);
|
fullLocation = this.baseStreamPath + baseLocation + encodeURIComponent(fileName);
|
||||||
} else {
|
} else {
|
||||||
// default to video but include subscription name param
|
// default to video but include subscription name param
|
||||||
baseLocation = this.type === 'audio' ? 'audio/' : 'video/';
|
baseLocation = 'video/';
|
||||||
fullLocation = this.baseStreamPath + baseLocation + encodeURIComponent(fileName) + '?subName=' + this.subscriptionName +
|
fullLocation = this.baseStreamPath + baseLocation + encodeURIComponent(fileName) + '?subName=' + this.subscriptionName +
|
||||||
'&subPlaylist=' + this.subPlaylist;
|
'&subPlaylist=' + this.subPlaylist;
|
||||||
}
|
}
|
||||||
@@ -237,12 +238,6 @@ export class PlayerComponent implements OnInit {
|
|||||||
onPlayerReady(api: VgAPI) {
|
onPlayerReady(api: VgAPI) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
|
|
||||||
// checks if volume has been previously set. if so, use that as default
|
|
||||||
if (localStorage.getItem('player_volume')) {
|
|
||||||
this.api.volume = parseFloat(localStorage.getItem('player_volume'));
|
|
||||||
}
|
|
||||||
setInterval(() => this.saveVolume(this.api), 2000)
|
|
||||||
|
|
||||||
this.api.getDefaultMedia().subscriptions.loadedMetadata.subscribe(this.playVideo.bind(this));
|
this.api.getDefaultMedia().subscriptions.loadedMetadata.subscribe(this.playVideo.bind(this));
|
||||||
this.api.getDefaultMedia().subscriptions.ended.subscribe(this.nextVideo.bind(this));
|
this.api.getDefaultMedia().subscriptions.ended.subscribe(this.nextVideo.bind(this));
|
||||||
|
|
||||||
@@ -251,13 +246,6 @@ export class PlayerComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
saveVolume(api) {
|
|
||||||
if (this.original_volume !== api.volume) {
|
|
||||||
localStorage.setItem('player_volume', api.volume)
|
|
||||||
this.original_volume = api.volume;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nextVideo() {
|
nextVideo() {
|
||||||
if (this.currentIndex === this.playlist.length - 1) {
|
if (this.currentIndex === this.playlist.length - 1) {
|
||||||
// dont continue playing
|
// dont continue playing
|
||||||
@@ -389,7 +377,7 @@ export class PlayerComponent implements OnInit {
|
|||||||
updatePlaylist() {
|
updatePlaylist() {
|
||||||
const fileNames = this.getFileNames();
|
const fileNames = this.getFileNames();
|
||||||
this.playlist_updating = true;
|
this.playlist_updating = true;
|
||||||
this.postsService.updatePlaylistFiles(this.id, fileNames, this.type).subscribe(res => {
|
this.postsService.updatePlaylist(this.id, fileNames, this.type).subscribe(res => {
|
||||||
this.playlist_updating = false;
|
this.playlist_updating = false;
|
||||||
if (res['success']) {
|
if (res['success']) {
|
||||||
const fileNamesEncoded = fileNames.join('|nvr|');
|
const fileNamesEncoded = fileNames.join('|nvr|');
|
||||||
|
|||||||
@@ -73,8 +73,6 @@ 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 redirect_not_required = window.location.href.includes('/player') || window.location.href.includes('/login');
|
|
||||||
|
|
||||||
// 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;
|
||||||
@@ -82,12 +80,10 @@ export class PostsService implements CanActivate {
|
|||||||
this.config = result['YoutubeDLMaterial'];
|
this.config = result['YoutubeDLMaterial'];
|
||||||
if (this.config['Advanced']['multi_user_mode']) {
|
if (this.config['Advanced']['multi_user_mode']) {
|
||||||
// login stuff
|
// login stuff
|
||||||
if (localStorage.getItem('jwt_token') && localStorage.getItem('jwt_token') !== 'null') {
|
if (localStorage.getItem('jwt_token')) {
|
||||||
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 (redirect_not_required) {
|
|
||||||
this.setInitialized();
|
|
||||||
} else {
|
} else {
|
||||||
this.sendToLogin();
|
this.sendToLogin();
|
||||||
}
|
}
|
||||||
@@ -234,10 +230,6 @@ export class PostsService implements CanActivate {
|
|||||||
return this.http.post(this.path + 'getVideoInfos', {fileNames: fileNames, type: type, urlMode: urlMode}, this.httpOptions);
|
return this.http.post(this.path + 'getVideoInfos', {fileNames: fileNames, type: type, urlMode: urlMode}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
getLogs(lines = 50) {
|
|
||||||
return this.http.post(this.path + 'logs', {lines: lines}, this.httpOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
isPinSet() {
|
isPinSet() {
|
||||||
return this.http.post(this.path + 'isPinSet', {}, this.httpOptions);
|
return this.http.post(this.path + 'isPinSet', {}, this.httpOptions);
|
||||||
}
|
}
|
||||||
@@ -274,12 +266,8 @@ export class PostsService implements CanActivate {
|
|||||||
type: type, uuid: uuid}, this.httpOptions);
|
type: type, uuid: uuid}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePlaylist(playlist) {
|
updatePlaylist(playlistID, fileNames, type) {
|
||||||
return this.http.post(this.path + 'updatePlaylist', {playlist: playlist}, this.httpOptions);
|
return this.http.post(this.path + 'updatePlaylist', {playlistID: playlistID,
|
||||||
}
|
|
||||||
|
|
||||||
updatePlaylistFiles(playlistID, fileNames, type) {
|
|
||||||
return this.http.post(this.path + 'updatePlaylistFiles', {playlistID: playlistID,
|
|
||||||
fileNames: fileNames,
|
fileNames: fileNames,
|
||||||
type: type}, this.httpOptions);
|
type: type}, this.httpOptions);
|
||||||
}
|
}
|
||||||
@@ -288,18 +276,17 @@ export class PostsService implements CanActivate {
|
|||||||
return this.http.post(this.path + 'deletePlaylist', {playlistID: playlistID, type: type}, this.httpOptions);
|
return this.http.post(this.path + 'deletePlaylist', {playlistID: playlistID, type: type}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
createSubscription(url, name, timerange = null, streamingOnly = false, audioOnly = false, customArgs = null, customFileOutput = null) {
|
createSubscription(url, name, timerange = null, streamingOnly = false) {
|
||||||
return this.http.post(this.path + 'subscribe', {url: url, name: name, timerange: timerange, streamingOnly: streamingOnly,
|
return this.http.post(this.path + 'subscribe', {url: url, name: name, timerange: timerange, streamingOnly: streamingOnly},
|
||||||
audioOnly: audioOnly, customArgs: customArgs, customFileOutput: customFileOutput}, this.httpOptions);
|
this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsubscribe(sub, deleteMode = false) {
|
unsubscribe(sub, deleteMode = false) {
|
||||||
return this.http.post(this.path + 'unsubscribe', {sub: sub, deleteMode: deleteMode}, this.httpOptions)
|
return this.http.post(this.path + 'unsubscribe', {sub: sub, deleteMode: deleteMode}, this.httpOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteSubscriptionFile(sub, file, deleteForever, file_uid) {
|
deleteSubscriptionFile(sub, file, deleteForever) {
|
||||||
return this.http.post(this.path + 'deleteSubscriptionFile', {sub: sub, file: file, deleteForever: deleteForever,
|
return this.http.post(this.path + 'deleteSubscriptionFile', {sub: sub, file: file, deleteForever: deleteForever}, this.httpOptions)
|
||||||
file_uid: file_uid}, this.httpOptions)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getSubscription(id) {
|
getSubscription(id) {
|
||||||
@@ -390,7 +377,6 @@ export class PostsService implements CanActivate {
|
|||||||
this.user = null;
|
this.user = null;
|
||||||
this.permissions = null;
|
this.permissions = null;
|
||||||
this.isLoggedIn = false;
|
this.isLoggedIn = false;
|
||||||
this.token = null;
|
|
||||||
localStorage.setItem('jwt_token', null);
|
localStorage.setItem('jwt_token', null);
|
||||||
if (this.router.url !== '/login') {
|
if (this.router.url !== '/login') {
|
||||||
this.router.navigate(['/login']);
|
this.router.navigate(['/login']);
|
||||||
@@ -411,14 +397,17 @@ export class PostsService implements CanActivate {
|
|||||||
const call = this.http.post(this.path + 'auth/register', {userid: username,
|
const call = this.http.post(this.path + 'auth/register', {userid: username,
|
||||||
username: username,
|
username: username,
|
||||||
password: password}, this.httpOptions);
|
password: password}, this.httpOptions);
|
||||||
|
/*call.subscribe(res => {
|
||||||
|
console.log(res['user']);
|
||||||
|
if (res['user']) {
|
||||||
|
// this.afterRegistration(res['user']);
|
||||||
|
}
|
||||||
|
});*/
|
||||||
return call;
|
return call;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendToLogin() {
|
sendToLogin() {
|
||||||
this.checkAdminCreationStatus();
|
this.checkAdminCreationStatus();
|
||||||
if (!this.initialized) {
|
|
||||||
this.setInitialized();
|
|
||||||
}
|
|
||||||
if (this.router.url === '/login') {
|
if (this.router.url === '/login') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,10 +152,6 @@
|
|||||||
<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>
|
||||||
@@ -275,7 +271,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-12 mt-2 mb-1">
|
<div class="col-12 mt-2 mb-1">
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label><ng-container i18n="Logger level select label">Select a logger level</ng-container></mat-label>
|
<mat-label><ng-container i18n="Logger level select label">Select a downloader</ng-container></mat-label>
|
||||||
<mat-select color="accent" [(ngModel)]="new_config['Advanced']['logger_level']">
|
<mat-select color="accent" [(ngModel)]="new_config['Advanced']['logger_level']">
|
||||||
<mat-option value="debug">Debug</mat-option>
|
<mat-option value="debug">Debug</mat-option>
|
||||||
<mat-option value="verbose">Verbose</mat-option>
|
<mat-option value="verbose">Verbose</mat-option>
|
||||||
@@ -311,13 +307,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<app-modify-users></app-modify-users>
|
<app-modify-users></app-modify-users>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
<mat-tab *ngIf="postsService.config" label="Logs" i18n-label="Logs settings label">
|
|
||||||
<ng-template matTabContent>
|
|
||||||
<div style="margin-left: 48px; margin-top: 24px; height: 340px">
|
|
||||||
<app-logs-viewer></app-logs-viewer>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
</mat-tab>
|
|
||||||
</mat-tab-group>
|
</mat-tab-group>
|
||||||
</mat-dialog-content>
|
</mat-dialog-content>
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { CookiesUploaderDialogComponent } from 'app/dialogs/cookies-uploader-dia
|
|||||||
})
|
})
|
||||||
export class SettingsComponent implements OnInit {
|
export class SettingsComponent implements OnInit {
|
||||||
all_locales = isoLangs;
|
all_locales = isoLangs;
|
||||||
supported_locales = ['en', 'es', 'de'];
|
supported_locales = ['en', 'es'];
|
||||||
initialLocale = localStorage.getItem('locale');
|
initialLocale = localStorage.getItem('locale');
|
||||||
|
|
||||||
initial_config = null;
|
initial_config = null;
|
||||||
|
|||||||
@@ -71,14 +71,14 @@ export class SubscriptionFileCardComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteAndRedownload() {
|
deleteAndRedownload() {
|
||||||
this.postsService.deleteSubscriptionFile(this.sub, this.file.id, false, this.file.uid).subscribe(res => {
|
this.postsService.deleteSubscriptionFile(this.sub, this.file.id, false).subscribe(res => {
|
||||||
this.reloadSubscription.emit(true);
|
this.reloadSubscription.emit(true);
|
||||||
this.openSnackBar(`Successfully deleted file: '${this.file.id}'`, 'Dismiss.');
|
this.openSnackBar(`Successfully deleted file: '${this.file.id}'`, 'Dismiss.');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteForever() {
|
deleteForever() {
|
||||||
this.postsService.deleteSubscriptionFile(this.sub, this.file.id, true, this.file.uid).subscribe(res => {
|
this.postsService.deleteSubscriptionFile(this.sub, this.file.id, true).subscribe(res => {
|
||||||
this.reloadSubscription.emit(true);
|
this.reloadSubscription.emit(true);
|
||||||
this.openSnackBar(`Successfully deleted file: '${this.file.id}'`, 'Dismiss.');
|
this.openSnackBar(`Successfully deleted file: '${this.file.id}'`, 'Dismiss.');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -92,9 +92,8 @@ export class SubscriptionComponent implements OnInit {
|
|||||||
if (this.subscription.streamingOnly) {
|
if (this.subscription.streamingOnly) {
|
||||||
this.router.navigate(['/player', {name: name, url: url}]);
|
this.router.navigate(['/player', {name: name, url: url}]);
|
||||||
} else {
|
} else {
|
||||||
this.router.navigate(['/player', {fileNames: name,
|
this.router.navigate(['/player', {fileNames: name, type: 'subscription', subscriptionName: this.subscription.name,
|
||||||
type: this.subscription.type ? this.subscription.type : 'video', subscriptionName: this.subscription.name,
|
subPlaylist: this.subscription.isPlaylist, uuid: this.postsService.user ? this.postsService.user.uid : null}]);
|
||||||
subPlaylist: this.subscription.isPlaylist, uuid: this.postsService.user ? this.postsService.user.uid : null}]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,7 @@
|
|||||||
"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",
|
||||||
@@ -38,7 +37,7 @@
|
|||||||
"Subscriptions": {
|
"Subscriptions": {
|
||||||
"allow_subscriptions": true,
|
"allow_subscriptions": true,
|
||||||
"subscriptions_base_path": "subscriptions/",
|
"subscriptions_base_path": "subscriptions/",
|
||||||
"subscriptions_check_interval": "30",
|
"subscriptions_check_interval": "300",
|
||||||
"subscriptions_use_youtubedl_archive": true
|
"subscriptions_use_youtubedl_archive": true
|
||||||
},
|
},
|
||||||
"Users": {
|
"Users": {
|
||||||
@@ -50,8 +49,7 @@
|
|||||||
"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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
{
|
|
||||||
"17f0ea5d2d7a262b0e875acc70475f102aee84e6": "Playlist erstellen",
|
|
||||||
"cff1428d10d59d14e45edec3c735a27b5482db59": "Name",
|
|
||||||
"f47e2d56dd8a145b2e9599da9730c049d52962a2": "Audiodateien",
|
|
||||||
"a52dae09be10ca3a65da918533ced3d3f4992238": "Videos",
|
|
||||||
"d9e83ac17026e70ef6e9c0f3240a3b2450367f40": "Youtube-dl Argumente ändern",
|
|
||||||
"7fc1946abe2b40f60059c6cd19975d677095fd19": "Simulierte neue Argumente",
|
|
||||||
"0b71824ae71972f236039bed43f8d2323e8fd570": "Argument hinzufügen",
|
|
||||||
"c8b0e59eb491f2ac7505f0fbab747062e6b32b23": "Nach Kategorie filtern",
|
|
||||||
"9eeb91caef5a50256dd87e1c4b7b3e8216479377": "Argument-Wert verwenden",
|
|
||||||
"25d8ad5eba2ec24e68295a27d6a4bb9b49e3dacd": "Argument-Wert",
|
|
||||||
"7de2451ed3fb8d8b847979bd3f0c740b970f167b": "Argument hinzufügen",
|
|
||||||
"d7b35c384aecd25a516200d6921836374613dfe7": "Abbrechen",
|
|
||||||
"b2623aee44b70c9a4ba1fce16c8a593b0a4c7974": "Ändern",
|
|
||||||
"038ebcb2a89155d90c24fa1c17bfe83dbadc3c20": "YouTube Downloader",
|
|
||||||
"6d2ec8898344c8955542b0542c942038ef28bb80": "Bitte geben Sie eine gültige URL ein.",
|
|
||||||
"a38ae1082fec79ba1f379978337385a539a28e73": "Qualität",
|
|
||||||
"4be966a9dcfbc9b54dfcc604b831c0289f847fa4": "URL verwenden",
|
|
||||||
"d3f02f845e62cebd75fde451ab8479d2a8ad784d": "Ansehen",
|
|
||||||
"4a9889d36910edc8323d7bab60858ab3da6d91df": "Nur Audio",
|
|
||||||
"96a01fafe135afc58b0f8071a4ab00234495ce18": "Multi-Download Modus",
|
|
||||||
"6a21ba5fb0ac804a525bf9ab168038c3ee88e661": "Download",
|
|
||||||
"6a3777f913cf3f288664f0632b9f24794fdcc24e": "Abbrechen",
|
|
||||||
"322ed150e02666fe2259c5b4614eac7066f4ffa0": "Erweitert",
|
|
||||||
"b7ffe7c6586d6f3f18a9246806a7c7d5538ab43e": "Simulierter Befehl:",
|
|
||||||
"4e4c721129466be9c3862294dc40241b64045998": "Benutzerdefinierte Argumente verwenden",
|
|
||||||
"ad2f8ac8b7de7945b80c8e424484da94e597125f": "Benutzerdefinierte Argumente",
|
|
||||||
"a6911c2157f1b775284bbe9654ce5eb30cf45d7f": "Die URL muss nicht angegeben werden, sondern nur der Teil danach. Argumente werden mit zwei Kommata getrennt: ,,",
|
|
||||||
"3a92a3443c65a52f37ca7efb8f453b35dbefbf29": "Benutzerdefinierte Ausgabe verwenden",
|
|
||||||
"d9c02face477f2f9cdaae318ccee5f89856851fb": "Benutzerdefinierte Ausgabe",
|
|
||||||
"fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7": "Dokumentation",
|
|
||||||
"19d1ae64d94d28a29b2c57ae8671aace906b5401": "Der Pfad ist relativ zum Konfigurations-Download-Pfad. Dateiendung auslassen.",
|
|
||||||
"8fad10737d3e3735a6699a4d89cbf6c20f6bb55f": "Authentifizierung verwenden",
|
|
||||||
"08c74dc9762957593b91f6eb5d65efdfc975bf48": "Benutzername",
|
|
||||||
"c32ef07f8803a223a83ed17024b38e8d82292407": "Passwort",
|
|
||||||
"4a0dada6e841a425de3e5006e6a04df26c644fa5": "Audio",
|
|
||||||
"9779715ac05308973d8f1c8658b29b986e92450f": "Ihre Audiodateien befinden sich hier",
|
|
||||||
"47546e45bbb476baaaad38244db444c427ddc502": "Playlisten",
|
|
||||||
"78bd81adb4609b68cfa4c589222bdc233ba1faaa": "Keine Wiedergabelisten verfügbar. Erstellen Sie eine aus Ihren heruntergeladenen Audiodateien, indem Sie auf das blaue Pluszeichen klicken.",
|
|
||||||
"9d2b62bb0b91e2e17fb4177a7e3d6756a2e6ee33": "Video",
|
|
||||||
"960582a8b9d7942716866ecfb7718309728f2916": "Ihre Videodateien befinden sich hier",
|
|
||||||
"0f59c46ca29e9725898093c9ea6b586730d0624e": "Keine Playlisten verfügbar. Erstellen Sie eine aus heruntergeladenen Audiodateien, indem Sie auf das blaue Pluszeichen klicken.",
|
|
||||||
"616e206cb4f25bd5885fc35925365e43cf5fb929": "Name:",
|
|
||||||
"c52db455cca9109ee47e1a612c3f4117c09eb71b": "URL:",
|
|
||||||
"c6eb45d085384903e53ab001a3513d1de6a1dbac": "Kanal:",
|
|
||||||
"109c6f4a5e46efb933612ededfaf52a13178b7e0": "Dateigröße:",
|
|
||||||
"bd630d8669b16e5f264ec4649d9b469fe03e5ff4": "Pfad:",
|
|
||||||
"a67e7d843cef735c79d5ef1c8ba4af3e758912bb": "Hochgeladen am:",
|
|
||||||
"f4e529ae5ffd73001d1ff4bbdeeb0a72e342e5c8": "Schließen",
|
|
||||||
"ca3dbbc7f3e011bffe32a10a3ea45cc84f30ecf1": "ID:",
|
|
||||||
"e684046d73bcee88e82f7ff01e2852789a05fc32": "Anzahl:",
|
|
||||||
"321e4419a943044e674beb55b8039f42a9761ca5": "Info",
|
|
||||||
"826b25211922a1b46436589233cb6f1a163d89b7": "Löschen",
|
|
||||||
"34504b488c24c27e68089be549f0eeae6ebaf30b": "Löschen und zur Blacklist hinzufügen",
|
|
||||||
"121cc5391cd2a5115bc2b3160379ee5b36cd7716": "Einstellungen",
|
|
||||||
"801b98c6f02fe3b32f6afa3ee854c99ed83474e6": "URL",
|
|
||||||
"54c512cca1923ab72faf1a0bd98d3d172469629a": "URL, über die auf diese Applikation zugegriffen wird, ohne Port.",
|
|
||||||
"cb2741a46e3560f6bc6dfd99d385e86b08b26d72": "Port",
|
|
||||||
"22e8f1d0423a3b784fe40fab187b92c06541b577": "Der gewünschte Port. Standard ist 17442.",
|
|
||||||
"d4477669a560750d2064051a510ef4d7679e2f3e": "Multi-User Modus",
|
|
||||||
"2eb03565fcdce7a7a67abc277a936a32fcf51557": "Benutzer Basispfad",
|
|
||||||
"a64505c41150663968e277ec9b3ddaa5f4838798": "Basispfad für Benutzer und deren heruntergeladene Videos.",
|
|
||||||
"cbe16a57be414e84b6a68309d08fad894df797d6": "Verschlüsselung verwenden",
|
|
||||||
"0c1875a79b7ecc792cc1bebca3e063e40b5764f9": "Dateipfad zum Zertifikat",
|
|
||||||
"736551b93461d2de64b118cf4043eee1d1c2cb2c": "Dateipfad zum Zertifikatsschlüssel",
|
|
||||||
"4e3120311801c4acd18de7146add2ee4a4417773": "Abonnements erlauben",
|
|
||||||
"4bee2a4bef2d26d37c9b353c278e24e5cd309ce3": "Abonnements Basispfad",
|
|
||||||
"bc9892814ee2d119ae94378c905ea440a249b84a": "Basispfad für Videos von abonnierten Kanälen und Wiedergabelisten. Dieser ist relativ zum Stammordner von YTDL-Material.",
|
|
||||||
"5bef4b25ba680da7fff06b86a91b1fc7e6a926e3": "Prüfintervall",
|
|
||||||
"0f56a7449b77630c114615395bbda4cab398efd8": "Einheit ist Sekunden, nur Zahlen sind erlaubt.",
|
|
||||||
"78e49b7339b4fa7184dd21bcaae107ce9b7076f6": "Youtube-DL Archiv verwenden",
|
|
||||||
"fa9fe4255231dd1cc6b29d3d254a25cb7c764f0f": "Mit der Archivfunktion",
|
|
||||||
"09006404cccc24b7a8f8d1ce0b39f2761ab841d8": "werden Informationen über Videos, welche durch ein Abonnement heruntergeladen wurden, in einem Textdokument festgehalten. Diese befinden sich in dem Archiv Unterverzeichnis vom Abonnementsordner.",
|
|
||||||
"29ed79a98fc01e7f9537777598e31dbde3aa7981": "Dadurch können Videos permanent gelöscht werden, ohne das Abonnement beenden zu müssen. Außerdem kann dadurch aufgezeichnet werden, welche Videos heruntergeladen wurden. Z. B. im Falle eines Datenverlusts.",
|
|
||||||
"27a56aad79d8b61269ed303f11664cc78bcc2522": "Design",
|
|
||||||
"ff7cee38a2259526c519f878e71b964f41db4348": "Standard",
|
|
||||||
"adb4562d2dbd3584370e44496969d58c511ecb63": "Dunkel",
|
|
||||||
"7a6bacee4c31cb5c0ac2d24274fb4610d8858602": "Designänderung erlauben",
|
|
||||||
"fe46ccaae902ce974e2441abe752399288298619": "Sprache",
|
|
||||||
"82421c3e46a0453a70c42900eab51d58d79e6599": "Allgemein",
|
|
||||||
"ab2756805742e84ad0cc0468f4be2d8aa9f855a5": "Audio Basispfad",
|
|
||||||
"c2c89cdf45d46ea64d2ed2f9ac15dfa4d77e26ca": "Dateipfad für Audio-Downloads. Dieser ist relativ zum Stammordner von YTDL-Material.",
|
|
||||||
"46826331da1949bd6fb74624447057099c9d20cd": "Video Basispfad",
|
|
||||||
"17c92e6d47a213fa95b5aa344b3f258147123f93": "Dateipfad für Video-Downloads. Dieser ist relativ zum Stammordner von YTDL-Material.",
|
|
||||||
"6b995e7130b4d667eaab6c5f61b362ace486d26d": "Globale benutzerdefinierte Argumente für Downloads auf der Startseite. Argumente werden durch zwei Kommata voneinander getrennt: ,,",
|
|
||||||
"0ba25ad86a240576c4f20a2fada4722ebba77b1e": "Downloader",
|
|
||||||
"61f8fd90b5f8cb20c70371feb2ee5e1fac5a9095": "Titel der Kopfzeile",
|
|
||||||
"78d3531417c0d4ba4c90f0d4ae741edc261ec8df": "Dateimanager aktivieren",
|
|
||||||
"a5a1be0a5df07de9eec57f5d2a86ed0204b2e75a": "Download-Manager aktivieren",
|
|
||||||
"c33bd5392b39dbed36b8e5a1145163a15d45835f": "Qualitätsauswahl erlauben",
|
|
||||||
"bda5508e24e0d77debb28bcd9194d8fefb1cfb92": "Nur Download Modus",
|
|
||||||
"09d31c803a7252658694e1e3176b97f5655a3fe3": "Multi-Download Modus erlauben",
|
|
||||||
"d8b47221b5af9e9e4cd5cb434d76fc0c91611409": "Einstellungen durch PIN schützen",
|
|
||||||
"f5ec7b2cdf87d41154f4fcbc86e856314409dcb9": "Neuen PIN festlegen",
|
|
||||||
"1c4dbce56d96b8974aac24a02f7ab2ee81415014": "Öffentliche API aktivieren",
|
|
||||||
"23bd81dcc30b74d06279a26d7a42e8901c1b124e": "Öffentlicher API-Schlüssel",
|
|
||||||
"41016a73d8ad85e6cb26dffa0a8fab9fe8f60d8e": "Dokumentation ansehen",
|
|
||||||
"1b258b258b4cc475ceb2871305b61756b0134f4a": "Generieren",
|
|
||||||
"d5d7c61349f3b0859336066e6d453fc35d334fe5": "YouTube API verwenden",
|
|
||||||
"ce10d31febb3d9d60c160750570310f303a22c22": "Youtube API-Schlüssel",
|
|
||||||
"8602e313cdfa7c4cc475ccbe86459fce3c3fd986": "Schlüsselgeneration ist einfach!",
|
|
||||||
"9b3cedfa83c6d7acb3210953289d1be4aab115c7": "Hier klicken",
|
|
||||||
"7f09776373995003161235c0c8d02b7f91dbc4df": "um die offizielle YoutubeDL-Material Chrome-Erweiterung manuell herunterzuladen.",
|
|
||||||
"5b5296423906ab3371fdb2b5a5aaa83acaa2ee52": "Die Erweiterung muss manuell installiert werden und in den Einstellungen der Erweiterung muss die Frontend-URL eingetragen werden.",
|
|
||||||
"9a2ec6da48771128384887525bdcac992632c863": "um die offizielle YoutubeDL-Material Firefox-Erweiterung direkt aus dem Firefox-Addon-Store zu installieren.",
|
|
||||||
"eb81be6b49e195e5307811d1d08a19259d411f37": "Detaillierte Anleitung.",
|
|
||||||
"cb17ff8fe3961cf90f44bee97c88a3f3347a7e55": "Die Frontend-URL muss in den Einstellungen der Erweiterung eingetragen werden.",
|
|
||||||
"61b81b11aad0b9d970ece2fce18405f07eac69c2": "Der untenstehende Link muss nur in die Lesezeichenleiste gezogen werden. Auf einer unterstützten Webseite können Sie danach einfach auf das Lesezeichen klicken, um das Video herunterzuladen.",
|
|
||||||
"c505d6c5de63cc700f0aaf8a4b31fae9e18024e5": "'Nur Audio' Lesezeichen generieren",
|
|
||||||
"d5f69691f9f05711633128b5a3db696783266b58": "Extra",
|
|
||||||
"5fab47f146b0a4b809dcebf3db9da94df6299ea1": "Standard Download-Agent verwenden",
|
|
||||||
"ec71e08aee647ea4a71fd6b7510c54d84a797ca6": "Downloader auswählen",
|
|
||||||
"dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8": "Erweiterte Download-Optionen aktivieren",
|
|
||||||
"bc2e854e111ecf2bd7db170da5e3c2ed08181d88": "Erweitert",
|
|
||||||
"37224420db54d4bc7696f157b779a7225f03ca9d": "Benutzerregistrierung zulassen",
|
|
||||||
"4d13a9cd5ed3dcee0eab22cb25198d43886942be": "Benutzer",
|
|
||||||
"52c9a103b812f258bcddc3d90a6e3f46871d25fe": "Speichern",
|
|
||||||
"fe8fd36dbf5deee1d56564965787a782a66eba44": "{VAR_SELECT, select, true {Schließen} false {Abbrechen} other {Andere}}",
|
|
||||||
"cec82c0a545f37420d55a9b6c45c20546e82f94e": "Über YoutubeDL-Material",
|
|
||||||
"199c17e5d6a419313af3c325f06dcbb9645ca618": "ist ein Open-Source YouTube-Downloader, der nach den Material-Design-Richtlinien von Google erstellt wurde. Sie können Ihre Lieblingsvideos reibungslos als Video- oder Audiodateien herunterladen und sogar Ihre Lieblingskanäle und Wiedergabelisten abonnieren, um auf dem Laufenden zu bleiben.",
|
|
||||||
"bc0ad0ee6630acb7fcb7802ec79f5a0ee943c1a7": "beinhaltet viele tolle Funktionen! API, Docker und Lokalisierung werden unter anderem unterstützt. Informieren Sie sich über alle unterstützten Funktionen auf Github.",
|
|
||||||
"a45e3b05f0529dc5246d70ef62304c94426d4c81": "Installierte Version:",
|
|
||||||
"e22f3a5351944f3a1a10cfc7da6f65dfbe0037fe": "Suche nach Updates ...",
|
|
||||||
"a16e92385b4fd9677bb830a4b796b8b79c113290": "Aktualisierung verfügbar",
|
|
||||||
"189b28aaa19b3c51c6111ad039c4fd5e2a22e370": "Sie können über das Einstellungsmenü aktualisieren.",
|
|
||||||
"b33536f59b94ec935a16bd6869d836895dc5300c": "Haben Sie einen Fehler gefunden oder einen Vorschlag?",
|
|
||||||
"e1f398f38ff1534303d4bb80bd6cece245f24016": "um ein Ticket zu öffnen.",
|
|
||||||
"42ff677ec14f111e88bd6cdd30145378e994d1bf": "Ihr Profil",
|
|
||||||
"ac9d09de42edca1296371e4d801349c9096ac8de": "UID:",
|
|
||||||
"a5ed099ffc9e96f6970df843289ade8a7d20ab9f": "Erstellt:",
|
|
||||||
"fa96f2137af0a24e6d6d54c598c0af7d5d5ad344": "Sie sind nicht angemeldet.",
|
|
||||||
"6765b4c916060f6bc42d9bb69e80377dbcb5e4e9": "Anmelden",
|
|
||||||
"bb694b49d408265c91c62799c2b3a7e3151c824d": "Ausloggen",
|
|
||||||
"a1dbca87b9f36d2b06a5cbcffb5814c4ae9b798a": "Admin-Konto erstellen",
|
|
||||||
"2d2adf3ca26a676bca2269295b7455a26fd26980": "Es wurde kein Standard-Administratorkonto erkannt. Ein Administratorkonto mit dem Benutzernamen \"admin\" wird erstellt und ein Passwort wird festgelegt.",
|
|
||||||
"70a67e04629f6d412db0a12d51820b480788d795": "Erstellen",
|
|
||||||
"994363f08f9fbfa3b3994ff7b35c6904fdff18d8": "Profil",
|
|
||||||
"004b222ff9ef9dd4771b777950ca1d0e4cd4348a": "Über",
|
|
||||||
"92eee6be6de0b11c924e3ab27db30257159c0a7c": "Startseite",
|
|
||||||
"357064ca9d9ac859eb618e28e8126fa32be049e2": "Abonnements",
|
|
||||||
"822fab38216f64e8166d368b59fe756ca39d301b": "Downloads",
|
|
||||||
"a249a5ae13e0835383885aaf697d2890cc3e53e9": "Playlist teilen",
|
|
||||||
"15da89490e04496ca9ea1e1b3d44fb5efd4a75d9": "Video teilen",
|
|
||||||
"1d540dcd271b316545d070f9d182c372d923aadd": "Audio teilen",
|
|
||||||
"1f6d14a780a37a97899dc611881e6bc971268285": "Freigabe aktivieren",
|
|
||||||
"6580b6a950d952df847cb3d8e7176720a740adc8": "Zeitstempel verwenden",
|
|
||||||
"4f2ed9e71a7c981db3e50ae2fedb28aff2ec4e6c": "Sekunden",
|
|
||||||
"3a6e5a6aa78ca864f6542410c5dafb6334538106": "In die Zwischenablage kopieren",
|
|
||||||
"5b3075e8dc3f3921ec316b0bd83b6d14a06c1a4f": "Änderungen speichern",
|
|
||||||
"4f8b2bb476981727ab34ed40fde1218361f92c45": "Details",
|
|
||||||
"e9aff8e6df2e2bf6299ea27bb2894c70bc48bd4d": "Ein Fehler ist aufgetreten:",
|
|
||||||
"77b0c73840665945b25bd128709aa64c8f017e1c": "Download Start:",
|
|
||||||
"08ff9375ec078065bcdd7637b7ea65fce2979266": "Download Ende:",
|
|
||||||
"ad127117f9471612f47d01eae09709da444a36a4": "Dateipfad(e):",
|
|
||||||
"a9806cf78ce00eb2613eeca11354a97e033377b8": "Abonnieren Sie eine Playlist oder einen Kanal",
|
|
||||||
"93efc99ae087fc116de708ecd3ace86ca237cf30": "Playlist oder Kanal URL",
|
|
||||||
"08f5d0ef937ae17feb1b04aff15ad88911e87baf": "Benutzerdefinierter Name",
|
|
||||||
"f3f62aa84d59f3a8b900cc9a7eec3ef279a7b4e7": "Dies ist optional",
|
|
||||||
"ea30873bd3f0d5e4fb2378eec3f0a1db77634a28": "Alle Uploads herunterladen",
|
|
||||||
"28a678e9cabf86e44c32594c43fa0e890135c20f": "Videos herunterladen aus den letzten",
|
|
||||||
"408ca4911457e84a348cecf214f02c69289aa8f1": "Nur Streaming Modus",
|
|
||||||
"d0336848b0c375a1c25ba369b3481ee383217a4f": "Abonnieren",
|
|
||||||
"e78c0d60ac39787f62c9159646fe0b3c1ed55a1d": "Typ:",
|
|
||||||
"a44d86aa1e6c20ced07aca3a7c081d8db9ded1c6": "Archiv:",
|
|
||||||
"8efc77bf327659c0fec1f518cf48a98cdcd9dddf": "Archiv exportieren",
|
|
||||||
"3042bd3ad8dffcfeca5fd1ae6159fd1047434e95": "Deabonnieren",
|
|
||||||
"e2319dec5b4ccfb6ed9f55ccabd63650a8fdf547": "Ihre Abonnements",
|
|
||||||
"807cf11e6ac1cde912496f764c176bdfdd6b7e19": "Kanäle",
|
|
||||||
"29b89f751593e1b347eef103891b7a1ff36ec03f": "Name nicht verfügbar. Kanal wird abgerufen...",
|
|
||||||
"4636cd4a1379c50d471e98786098c4d39e1e82ad": "Sie haben keine Kanäle abonniert.",
|
|
||||||
"2e0a410652cb07d069f576b61eab32586a18320d": "Name nicht verfügbar. Playlist wird abgerufen...",
|
|
||||||
"587b57ced54965d8874c3fd0e9dfedb987e5df04": "Sie haben keine Playlisten abonniert.",
|
|
||||||
"7e892ba15f2c6c17e83510e273b3e10fc32ea016": "Suchen",
|
|
||||||
"2054791b822475aeaea95c0119113de3200f5e1c": "Länge:",
|
|
||||||
"94e01842dcee90531caa52e4147f70679bac87fe": "Löschen und erneut herunterladen",
|
|
||||||
"2031adb51e07a41844e8ba7704b054e98345c9c1": "Permanent löschen",
|
|
||||||
"91ecce65f1d23f9419d1c953cd6b7bc7f91c110e": "Updater",
|
|
||||||
"1372e61c5bd06100844bd43b98b016aabc468f62": "Wählen Sie eine Version:",
|
|
||||||
"cfc2f436ec2beffb042e7511a73c89c372e86a6c": "Registrieren",
|
|
||||||
"a1ad8b1be9be43b5183bd2c3186d4e19496f2a0b": "Sitzungs-ID:",
|
|
||||||
"eb98135e35af26a9a326ee69bd8ff104d36dd8ec": "(aktuell)",
|
|
||||||
"7117fc42f860e86d983bfccfcf2654e5750f3406": "Zurzeit sind keine Downloads verfügbar.",
|
|
||||||
"b7ff2e2b909c53abe088fe60b9f4b6ac7757247f": "Nutzer registrieren",
|
|
||||||
"024886ca34a6f309e3e51c2ed849320592c3faaa": "Benutzername",
|
|
||||||
"2bd201aea09e43fbfd3cd15ec0499b6755302329": "Benutzer verwalten",
|
|
||||||
"29c97c8e76763bb15b6d515648fa5bd1eb0f7510": "Benutzer-UID",
|
|
||||||
"e70e209561583f360b1e9cefd2cbb1fe434b6229": "Neues Passwort",
|
|
||||||
"6498fa1b8f563988f769654a75411bb8060134b9": "Neues Passwort festlegen",
|
|
||||||
"40da072004086c9ec00d125165da91eaade7f541": "Standard verwenden",
|
|
||||||
"4f20f2d5a6882190892e58b85f6ccbedfa737952": "Ja",
|
|
||||||
"3d3ae7deebc5949b0c1c78b9847886a94321d9fd": "Nein",
|
|
||||||
"57c6c05d8ebf4ef1180c2705033c044f655bb2c4": "Rolle verwalten",
|
|
||||||
"746f64ddd9001ac456327cd9a3d5152203a4b93c": "Benutzername",
|
|
||||||
"52c1447c1ec9570a2a3025c7e566557b8d19ed92": "Rolle",
|
|
||||||
"59a8c38db3091a63ac1cb9590188dc3a972acfb3": "Aktionen",
|
|
||||||
"4d92a0395dd66778a931460118626c5794a3fc7a": "Benutzer hinzufügen",
|
|
||||||
"b0d7dd8a1b0349622d6e0c6e643e24a9ea0efa1d": "Rolle bearbeiten"
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user