mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-03-21 20:20:57 +03:00
Merge pull request #136 from Tzahi12345/subscriptions-custom-path
Subscriptions V3
This commit is contained in:
230
backend/app.js
230
backend/app.js
@@ -13,6 +13,8 @@ var express = require("express");
|
||||
var bodyParser = require("body-parser");
|
||||
var archiver = require('archiver');
|
||||
var unzipper = require('unzipper');
|
||||
var db_api = require('./db')
|
||||
var utils = require('./utils')
|
||||
var mergeFiles = require('merge-files');
|
||||
const low = require('lowdb')
|
||||
var ProgressBar = require('progress');
|
||||
@@ -73,8 +75,9 @@ const logger = winston.createLogger({
|
||||
});
|
||||
|
||||
config_api.initialize(logger);
|
||||
subscriptions_api.initialize(db, 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');
|
||||
|
||||
@@ -191,27 +194,12 @@ app.use(bodyParser.json());
|
||||
// use passport
|
||||
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
|
||||
|
||||
async function checkMigrations() {
|
||||
return new Promise(async resolve => {
|
||||
// 3.5->3.6 migration
|
||||
const files_to_db_migration_complete = db.get('files_to_db_migration_complete').value();
|
||||
const files_to_db_migration_complete = true; // migration phased out! previous code: db.get('files_to_db_migration_complete').value();
|
||||
|
||||
if (!files_to_db_migration_complete) {
|
||||
logger.info('Beginning migration: 3.5->3.6+')
|
||||
@@ -236,7 +224,7 @@ async function runFilesToDBMigration() {
|
||||
const file_already_in_db = db.get('files.audio').find({id: file_obj.id}).value();
|
||||
if (!file_already_in_db) {
|
||||
logger.verbose(`Migrating file ${file_obj.id}`);
|
||||
registerFileDB(file_obj.id + '.mp3', 'audio');
|
||||
db_api.registerFileDB(file_obj.id + '.mp3', 'audio');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +233,7 @@ async function runFilesToDBMigration() {
|
||||
const file_already_in_db = db.get('files.video').find({id: file_obj.id}).value();
|
||||
if (!file_already_in_db) {
|
||||
logger.verbose(`Migrating file ${file_obj.id}`);
|
||||
registerFileDB(file_obj.id + '.mp4', 'video');
|
||||
db_api.registerFileDB(file_obj.id + '.mp4', 'video');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -654,6 +642,11 @@ async function watchSubscriptions() {
|
||||
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);
|
||||
setTimeout(async () => {
|
||||
await subscriptions_api.getVideosForSub(sub, sub.user_uid);
|
||||
@@ -700,7 +693,7 @@ function getMp3s() {
|
||||
var stats = fs.statSync(file);
|
||||
|
||||
var id = file_path.substring(0, file_path.length-4);
|
||||
var jsonobj = getJSONMp3(id);
|
||||
var jsonobj = utils.getJSONMp3(id, audioFolderPath);
|
||||
if (!jsonobj) continue;
|
||||
var title = jsonobj.title;
|
||||
var url = jsonobj.webpage_url;
|
||||
@@ -713,7 +706,7 @@ function getMp3s() {
|
||||
var thumbnail = jsonobj.thumbnail;
|
||||
var duration = jsonobj.duration;
|
||||
var isaudio = true;
|
||||
var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date);
|
||||
var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date);
|
||||
mp3s.push(file_obj);
|
||||
}
|
||||
return mp3s;
|
||||
@@ -729,7 +722,7 @@ function getMp4s(relative_path = true) {
|
||||
var stats = fs.statSync(file);
|
||||
|
||||
var id = file_path.substring(0, file_path.length-4);
|
||||
var jsonobj = getJSONMp4(id);
|
||||
var jsonobj = utils.getJSONMp4(id, videoFolderPath);
|
||||
if (!jsonobj) continue;
|
||||
var title = jsonobj.title;
|
||||
var url = jsonobj.webpage_url;
|
||||
@@ -742,7 +735,7 @@ function getMp4s(relative_path = true) {
|
||||
var size = stats.size;
|
||||
|
||||
var isaudio = false;
|
||||
var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date);
|
||||
var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file, upload_date);
|
||||
mp4s.push(file_obj);
|
||||
}
|
||||
return mp4s;
|
||||
@@ -750,14 +743,14 @@ function getMp4s(relative_path = true) {
|
||||
|
||||
function getThumbnailMp3(name)
|
||||
{
|
||||
var obj = getJSONMp3(name);
|
||||
var obj = utils.getJSONMp3(name, audioFolderPath);
|
||||
var thumbnailLink = obj.thumbnail;
|
||||
return thumbnailLink;
|
||||
}
|
||||
|
||||
function getThumbnailMp4(name)
|
||||
{
|
||||
var obj = getJSONMp4(name);
|
||||
var obj = utils.getJSONMp4(name, videoFolderPath);
|
||||
var thumbnailLink = obj.thumbnail;
|
||||
return thumbnailLink;
|
||||
}
|
||||
@@ -794,54 +787,6 @@ function getFileSizeMp4(name)
|
||||
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)
|
||||
{
|
||||
var partPath = audioFolderPath+name+".mp3.part";
|
||||
@@ -965,7 +910,7 @@ async function deleteAudioFile(name, blacklistMode = false) {
|
||||
|
||||
// get ID from JSON
|
||||
|
||||
var jsonobj = getJSONMp3(name);
|
||||
var jsonobj = utils.getJSONMp3(name, audioFolderPath);
|
||||
let id = null;
|
||||
if (jsonobj) id = jsonobj.id;
|
||||
|
||||
@@ -1023,7 +968,7 @@ async function deleteVideoFile(name, customPath = null, blacklistMode = false) {
|
||||
|
||||
// get ID from JSON
|
||||
|
||||
var jsonobj = getJSONMp4(name);
|
||||
var jsonobj = utils.getJSONMp4(name, videoFolderPath);
|
||||
let id = null;
|
||||
if (jsonobj) id = jsonobj.id;
|
||||
|
||||
@@ -1077,7 +1022,7 @@ function recFindByExt(base,ext,files,result)
|
||||
)
|
||||
return result
|
||||
}
|
||||
|
||||
/*
|
||||
function registerFileDB(file_path, type, multiUserMode = null) {
|
||||
const file_id = file_path.substring(0, file_path.length-4);
|
||||
const file_object = generateFileObject(file_id, type, multiUserMode && multiUserMode.file_path);
|
||||
@@ -1094,7 +1039,7 @@ function registerFileDB(file_path, type, multiUserMode = null) {
|
||||
|
||||
if (multiUserMode) {
|
||||
auth_api.registerUserFile(multiUserMode.user, file_object, type);
|
||||
} else {
|
||||
} else if (type === 'audio' || type === 'video') {
|
||||
// remove existing video if overwriting
|
||||
db.get(`files.${type}`)
|
||||
.remove({
|
||||
@@ -1104,13 +1049,15 @@ function registerFileDB(file_path, type, multiUserMode = null) {
|
||||
db.get(`files.${type}`)
|
||||
.push(file_object)
|
||||
.write();
|
||||
} else if (type == 'subscription') {
|
||||
|
||||
}
|
||||
|
||||
return file_object['uid'];
|
||||
}
|
||||
|
||||
function generateFileObject(id, type, customPath = null) {
|
||||
var jsonobj = (type === 'audio') ? getJSONMp3(id, customPath, true) : getJSONMp4(id, customPath, true);
|
||||
var jsonobj = (type === 'audio') ? utils.getJSONMp3(id, customPath, true) : utils.getJSONMp4(id, customPath, true);
|
||||
if (!jsonobj) {
|
||||
return null;
|
||||
}
|
||||
@@ -1130,10 +1077,10 @@ function generateFileObject(id, type, customPath = null) {
|
||||
var thumbnail = jsonobj.thumbnail;
|
||||
var duration = jsonobj.duration;
|
||||
var isaudio = type === 'audio';
|
||||
var file_obj = new File(id, title, thumbnail, isaudio, duration, url, uploader, size, file_path, upload_date);
|
||||
var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file_path, upload_date);
|
||||
return file_obj;
|
||||
}
|
||||
|
||||
*/
|
||||
// replaces .webm with appropriate extension
|
||||
function getTrueFileName(unfixed_path, type) {
|
||||
let fixed_path = unfixed_path;
|
||||
@@ -1290,7 +1237,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) {
|
||||
}
|
||||
|
||||
// registers file in DB
|
||||
file_uid = registerFileDB(full_file_path.substring(fileFolderPath.length, full_file_path.length), type, multiUserMode);
|
||||
file_uid = db_api.registerFileDB(full_file_path.substring(fileFolderPath.length, full_file_path.length), type, multiUserMode);
|
||||
|
||||
if (file_name) file_names.push(file_name);
|
||||
}
|
||||
@@ -1429,7 +1376,7 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) {
|
||||
|
||||
// registers file in DB
|
||||
const base_file_name = video_info._filename.substring(fileFolderPath.length, video_info._filename.length);
|
||||
file_uid = registerFileDB(base_file_name, type, multiUserMode);
|
||||
file_uid = db_api.registerFileDB(base_file_name, type, multiUserMode);
|
||||
|
||||
if (options.merged_string !== null && options.merged_string !== undefined) {
|
||||
let current_merged_archive = fs.readFileSync(path.join(fileFolderPath, `merged_${type}.txt`), 'utf8');
|
||||
@@ -2152,14 +2099,17 @@ app.post('/api/subscribe', optionalJwt, async (req, res) => {
|
||||
let url = req.body.url;
|
||||
let timerange = req.body.timerange;
|
||||
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;
|
||||
|
||||
const new_sub = {
|
||||
name: name,
|
||||
url: url,
|
||||
id: uuid(),
|
||||
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
|
||||
@@ -2167,6 +2117,14 @@ app.post('/api/subscribe', optionalJwt, async (req, res) => {
|
||||
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);
|
||||
|
||||
if (result_obj.success) {
|
||||
@@ -2202,10 +2160,11 @@ app.post('/api/unsubscribe', optionalJwt, async (req, res) => {
|
||||
app.post('/api/deleteSubscriptionFile', optionalJwt, async (req, res) => {
|
||||
let deleteForever = req.body.deleteForever;
|
||||
let file = req.body.file;
|
||||
let file_uid = req.body.file_uid;
|
||||
let sub = req.body.sub;
|
||||
let user_uid = req.isAuthenticated() ? req.user.uid : null;
|
||||
|
||||
let success = await subscriptions_api.deleteSubscriptionFile(sub, file, deleteForever, user_uid);
|
||||
let success = await subscriptions_api.deleteSubscriptionFile(sub, file, deleteForever, file_uid, user_uid);
|
||||
|
||||
if (success) {
|
||||
res.send({
|
||||
@@ -2232,45 +2191,49 @@ app.post('/api/getSubscription', optionalJwt, async (req, res) => {
|
||||
|
||||
// get sub videos
|
||||
if (subscription.name && !subscription.streamingOnly) {
|
||||
let base_path = null;
|
||||
if (user_uid)
|
||||
base_path = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
|
||||
else
|
||||
base_path = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||
var parsed_files = subscription.videos;
|
||||
if (!parsed_files) {
|
||||
parsed_files = [];
|
||||
let base_path = null;
|
||||
if (user_uid)
|
||||
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 files;
|
||||
try {
|
||||
files = recFindByExt(appended_base_path, 'mp4');
|
||||
} catch(e) {
|
||||
files = null;
|
||||
logger.info('Failed to get folder for subscription: ' + subscription.name + ' at path ' + appended_base_path);
|
||||
res.sendStatus(500);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
let appended_base_path = path.join(base_path, (subscription.isPlaylist ? 'playlists' : 'channels'), subscription.name, '/');
|
||||
let files;
|
||||
try {
|
||||
files = recFindByExt(appended_base_path, 'mp4');
|
||||
} catch(e) {
|
||||
files = null;
|
||||
logger.info('Failed to get folder for subscription: ' + subscription.name + ' at path ' + appended_base_path);
|
||||
res.sendStatus(500);
|
||||
return;
|
||||
}
|
||||
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({
|
||||
subscription: subscription,
|
||||
@@ -2282,7 +2245,7 @@ app.post('/api/getSubscription', optionalJwt, async (req, res) => {
|
||||
if (subscription.videos) {
|
||||
for (let i = 0; i < subscription.videos.length; i++) {
|
||||
const video = subscription.videos[i];
|
||||
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));
|
||||
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));
|
||||
}
|
||||
}
|
||||
res.send({
|
||||
@@ -2522,7 +2485,8 @@ app.post('/api/downloadFile', optionalJwt, async (req, res) => {
|
||||
basePath = path.join(usersFileFolder, req.user.uid, 'subscriptions');
|
||||
else
|
||||
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 {
|
||||
for (let i = 0; i < fileNames.length; i++) {
|
||||
@@ -2723,9 +2687,21 @@ app.get('/api/audio/:id', optionalJwt, function(req , res){
|
||||
var head;
|
||||
let id = decodeURIComponent(req.params.id);
|
||||
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()) {
|
||||
let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
||||
file_path = path.join(usersFileFolder, req.user.uid, 'audio', id + '.mp3');
|
||||
if (optionalParams['subName']) {
|
||||
const isPlaylist = optionalParams['subPlaylist'];
|
||||
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, '\'');
|
||||
const stat = fs.statSync(file_path)
|
||||
|
||||
103
backend/db.js
Normal file
103
backend/db.js
Normal file
@@ -0,0 +1,103 @@
|
||||
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;
|
||||
}
|
||||
|
||||
// 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 getAppendedBasePathSub(sub, base_path) {
|
||||
return path.join(base_path, (sub.isPlaylist ? 'playlists/' : 'channels/'), sub.name);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initialize: initialize,
|
||||
registerFileDB: registerFileDB
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
1
backend/public/1-es5.58a075d60532093bfbbf.js
Normal file
1
backend/public/1-es5.58a075d60532093bfbbf.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -14,5 +14,5 @@
|
||||
<link rel="stylesheet" href="styles.5112d6db78cf21541598.css"></head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
<script src="runtime-es2015.c1a751ca64bf9326c132.js" type="module"></script><script src="runtime-es5.c1a751ca64bf9326c132.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.d59790a263b5bfaf4a25.js" type="module"></script><script src="runtime-es5.d59790a263b5bfaf4a25.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>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],l=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=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,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(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,a),t.l=!0,t.exports}a.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,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"-es2015."+{1:"a4914bd653aa99fdc1ed"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.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:i})}),12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var l=0;l<i.length;l++)r(i[l]);var f=c;t()}([]);
|
||||
1
backend/public/runtime-es2015.d59790a263b5bfaf4a25.js
Normal file
1
backend/public/runtime-es2015.d59790a263b5bfaf4a25.js
Normal file
@@ -0,0 +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:"58a075d60532093bfbbf"}[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 +0,0 @@
|
||||
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],l=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=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,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(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,a),t.l=!0,t.exports}a.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,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"-es5."+{1:"a4914bd653aa99fdc1ed"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.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:i})}),12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var l=0;l<i.length;l++)r(i[l]);var f=c;t()}([]);
|
||||
1
backend/public/runtime-es5.d59790a263b5bfaf4a25.js
Normal file
1
backend/public/runtime-es5.d59790a263b5bfaf4a25.js
Normal file
@@ -0,0 +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:"58a075d60532093bfbbf"}[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,17 +6,20 @@ var path = require('path');
|
||||
|
||||
var youtubedl = require('youtube-dl');
|
||||
const config_api = require('./config');
|
||||
var utils = require('./utils')
|
||||
|
||||
const debugMode = process.env.YTDL_MODE === 'debug';
|
||||
|
||||
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 }
|
||||
var db_api = null;
|
||||
|
||||
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 initialize(input_db, input_users_db, input_logger) {
|
||||
setDB(input_db, input_users_db);
|
||||
function initialize(input_db, input_users_db, input_logger, input_db_api) {
|
||||
setDB(input_db, input_users_db, input_db_api);
|
||||
setLogger(input_logger);
|
||||
}
|
||||
|
||||
@@ -28,6 +31,7 @@ async function subscribe(sub, user_uid = null) {
|
||||
return new Promise(async resolve => {
|
||||
// sub should just have url and name. here we will get isPlaylist and path
|
||||
sub.isPlaylist = sub.url.includes('playlist');
|
||||
sub.videos = [];
|
||||
|
||||
let url_exists = false;
|
||||
|
||||
@@ -44,15 +48,25 @@ async function subscribe(sub, user_uid = null) {
|
||||
}
|
||||
|
||||
// add sub to db
|
||||
if (user_uid)
|
||||
let sub_db = null;
|
||||
if (user_uid) {
|
||||
users_db.get('users').find({uid: user_uid}).get('subscriptions').push(sub).write();
|
||||
else
|
||||
sub_db = users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id});
|
||||
} else {
|
||||
db.get('subscriptions').push(sub).write();
|
||||
|
||||
sub_db = db.get('subscriptions').find({id: sub.id});
|
||||
}
|
||||
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.sub = sub;
|
||||
getVideosForSub(sub, user_uid);
|
||||
resolve(result_obj);
|
||||
});
|
||||
|
||||
@@ -160,20 +174,26 @@ async function unsubscribe(sub, deleteMode, user_uid = null) {
|
||||
|
||||
}
|
||||
|
||||
async function deleteSubscriptionFile(sub, file, deleteForever, user_uid = null) {
|
||||
async function deleteSubscriptionFile(sub, file, deleteForever, file_uid = null, user_uid = null) {
|
||||
let basePath = null;
|
||||
if (user_uid)
|
||||
let sub_db = null;
|
||||
if (user_uid) {
|
||||
basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
|
||||
else
|
||||
sub_db = users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id});
|
||||
} else {
|
||||
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 appendedBasePath = getAppendedBasePath(sub, basePath);
|
||||
const name = file;
|
||||
let retrievedID = null;
|
||||
sub_db.get('videos').remove({uid: file_uid}).write();
|
||||
return new Promise(resolve => {
|
||||
let filePath = appendedBasePath;
|
||||
const ext = (sub.type && sub.type === 'audio') ? '.mp3' : '.mp4'
|
||||
var jsonPath = path.join(__dirname,filePath,name+'.info.json');
|
||||
var videoFilePath = path.join(__dirname,filePath,name+'.mp4');
|
||||
var videoFilePath = path.join(__dirname,filePath,name+ext);
|
||||
var imageFilePath = path.join(__dirname,filePath,name+'.jpg');
|
||||
|
||||
jsonExists = fs.existsSync(jsonPath);
|
||||
@@ -237,13 +257,45 @@ async function getVideosForSub(sub, user_uid = null) {
|
||||
const useArchive = config_api.getConfigItem('ytdl_subscriptions_use_youtubedl_archive');
|
||||
|
||||
let appendedBasePath = null
|
||||
if (sub.name) {
|
||||
appendedBasePath = getAppendedBasePath(sub, basePath);
|
||||
} else {
|
||||
appendedBasePath = path.join(basePath, (sub.isPlaylist ? 'playlists/%(playlist_title)s' : 'channels/%(uploader)s'));
|
||||
appendedBasePath = getAppendedBasePath(sub, basePath);
|
||||
|
||||
let multiUserMode = null;
|
||||
if (user_uid) {
|
||||
multiUserMode = {
|
||||
user: user_uid,
|
||||
file_path: appendedBasePath
|
||||
}
|
||||
}
|
||||
|
||||
let downloadConfig = ['-o', appendedBasePath + '/%(title)s.mp4', '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4', '-ciw', '--write-info-json', '--print-json'];
|
||||
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 {
|
||||
qualityPath = ['-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4']
|
||||
}
|
||||
|
||||
downloadConfig.push(...qualityPath)
|
||||
|
||||
if (sub.custom_args) {
|
||||
customArgsArray = sub.custom_args.split(',,');
|
||||
if (customArgsArray.indexOf('-f') !== -1) {
|
||||
// if custom args has a custom quality, replce the original quality with that of custom args
|
||||
const original_output_index = downloadConfig.indexOf('-f');
|
||||
downloadConfig.splice(original_output_index, 2);
|
||||
}
|
||||
downloadConfig.push(...customArgsArray);
|
||||
}
|
||||
|
||||
let archive_dir = null;
|
||||
let archive_path = null;
|
||||
@@ -286,7 +338,7 @@ async function getVideosForSub(sub, user_uid = null) {
|
||||
const outputs = err.stdout.split(/\r\n|\r|\n/);
|
||||
for (let i = 0; i < outputs.length; i++) {
|
||||
const output = JSON.parse(outputs[i]);
|
||||
handleOutputJSON(sub, sub_db, output, i === 0)
|
||||
handleOutputJSON(sub, sub_db, output, i === 0, multiUserMode)
|
||||
if (err.stderr.includes(output['id']) && archive_path) {
|
||||
// we found a video that errored! add it to the archive to prevent future errors
|
||||
fs.appendFileSync(archive_path, output['id']);
|
||||
@@ -315,7 +367,7 @@ async function getVideosForSub(sub, user_uid = null) {
|
||||
}
|
||||
|
||||
const reset_videos = i === 0;
|
||||
handleOutputJSON(sub, sub_db, output_json, reset_videos);
|
||||
handleOutputJSON(sub, sub_db, output_json, multiUserMode, reset_videos);
|
||||
|
||||
// TODO: Potentially store downloaded files in db?
|
||||
|
||||
@@ -323,10 +375,12 @@ async function getVideosForSub(sub, user_uid = null) {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
}, err => {
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
function handleOutputJSON(sub, sub_db, output_json, reset_videos = false) {
|
||||
function handleOutputJSON(sub, sub_db, output_json, multiUserMode = null, reset_videos = false) {
|
||||
if (sub.streamingOnly) {
|
||||
if (reset_videos) {
|
||||
sub_db.assign({videos: []}).write();
|
||||
@@ -337,6 +391,9 @@ function handleOutputJSON(sub, sub_db, output_json, reset_videos = false) {
|
||||
|
||||
// add to db
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
78
backend/utils.js
Normal file
78
backend/utils.js
Normal file
@@ -0,0 +1,78 @@
|
||||
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'));
|
||||
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 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'));
|
||||
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;
|
||||
}
|
||||
|
||||
// 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,
|
||||
File: File
|
||||
}
|
||||
@@ -3,16 +3,20 @@
|
||||
<mat-dialog-content>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="col-12 mb-4">
|
||||
<mat-form-field color="accent">
|
||||
<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-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<mat-form-field color="accent">
|
||||
<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>
|
||||
</div>
|
||||
<div class="col-12 mt-3">
|
||||
@@ -31,9 +35,33 @@
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div>
|
||||
<mat-checkbox [(ngModel)]="streamingOnlyMode"><ng-container i18n="Streaming-only mode">Streaming-only mode</ng-container></mat-checkbox>
|
||||
<mat-checkbox [(ngModel)]="audioOnlyMode"><ng-container i18n="Streaming-only mode">Audio-only mode</ng-container></mat-checkbox>
|
||||
</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>
|
||||
</mat-dialog-content>
|
||||
|
||||
@@ -6,3 +6,8 @@
|
||||
.mat-spinner {
|
||||
margin-left: 5%;
|
||||
}
|
||||
|
||||
.args-edit-button {
|
||||
position: absolute;
|
||||
margin-left: 10px;
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { MatDialogRef, MatDialog } from '@angular/material/dialog';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
import { ArgModifierDialogComponent } from '../arg-modifier-dialog/arg-modifier-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-subscribe-dialog',
|
||||
@@ -22,6 +23,12 @@ export class SubscribeDialogComponent implements OnInit {
|
||||
// no videos actually downloaded, just streamed
|
||||
streamingOnlyMode = false;
|
||||
|
||||
// audio only mode
|
||||
audioOnlyMode = false;
|
||||
|
||||
customFileOutput = '';
|
||||
customArgs = '';
|
||||
|
||||
time_units = [
|
||||
'day',
|
||||
'week',
|
||||
@@ -31,6 +38,7 @@ export class SubscribeDialogComponent implements OnInit {
|
||||
|
||||
constructor(private postsService: PostsService,
|
||||
private snackBar: MatSnackBar,
|
||||
private dialog: MatDialog,
|
||||
public dialogRef: MatDialogRef<SubscribeDialogComponent>) { }
|
||||
|
||||
ngOnInit() {
|
||||
@@ -49,7 +57,8 @@ export class SubscribeDialogComponent implements OnInit {
|
||||
if (!this.download_all) {
|
||||
timerange = 'now-' + this.timerange_amount.toString() + this.timerange_unit;
|
||||
}
|
||||
this.postsService.createSubscription(this.url, this.name, timerange, this.streamingOnlyMode).subscribe(res => {
|
||||
this.postsService.createSubscription(this.url, this.name, timerange, this.streamingOnlyMode,
|
||||
this.audioOnlyMode, this.customArgs, this.customFileOutput).subscribe(res => {
|
||||
this.subscribing = false;
|
||||
if (res['new_sub']) {
|
||||
this.dialogRef.close(res['new_sub']);
|
||||
@@ -63,6 +72,20 @@ 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 = '') {
|
||||
this.snackBar.open(message, action, {
|
||||
duration: 2000,
|
||||
|
||||
@@ -439,10 +439,11 @@ export class MainComponent implements OnInit {
|
||||
|
||||
public removeFromMp3(name: string) {
|
||||
for (let i = 0; i < this.mp3s.length; i++) {
|
||||
if (this.mp3s[i].id === name) {
|
||||
if (this.mp3s[i].id === name || this.mp3s[i].id + '.mp3' === name) {
|
||||
this.mp3s.splice(i, 1);
|
||||
}
|
||||
}
|
||||
this.getMp3s();
|
||||
}
|
||||
|
||||
public removePlaylistMp3(playlistID, index) {
|
||||
@@ -457,10 +458,11 @@ export class MainComponent implements OnInit {
|
||||
|
||||
public removeFromMp4(name: string) {
|
||||
for (let i = 0; i < this.mp4s.length; i++) {
|
||||
if (this.mp4s[i].id === name) {
|
||||
if (this.mp4s[i].id === name || this.mp4s[i].id + '.mp4' === name) {
|
||||
this.mp4s.splice(i, 1);
|
||||
}
|
||||
}
|
||||
this.getMp4s();
|
||||
}
|
||||
|
||||
public removePlaylistMp4(playlistID, index) {
|
||||
|
||||
@@ -127,7 +127,7 @@ export class PlayerComponent implements OnInit {
|
||||
this.currentItem = this.playlist[0];
|
||||
this.currentIndex = 0;
|
||||
this.show_player = true;
|
||||
} else if (this.type === 'subscription' || this.fileNames) {
|
||||
} else if (this.subscriptionName || this.fileNames) {
|
||||
this.show_player = true;
|
||||
this.parseFileNames();
|
||||
}
|
||||
@@ -181,9 +181,6 @@ export class PlayerComponent implements OnInit {
|
||||
fileType = 'audio/mp3';
|
||||
} else if (this.type === 'video') {
|
||||
fileType = 'video/mp4';
|
||||
} else if (this.type === 'subscription') {
|
||||
// only supports mp4 for now
|
||||
fileType = 'video/mp4';
|
||||
} else {
|
||||
// error
|
||||
console.error('Must have valid file type! Use \'audio\', \'video\', or \'subscription\'.');
|
||||
@@ -198,7 +195,7 @@ export class PlayerComponent implements OnInit {
|
||||
fullLocation = this.baseStreamPath + baseLocation + encodeURIComponent(fileName);
|
||||
} else {
|
||||
// default to video but include subscription name param
|
||||
baseLocation = 'video/';
|
||||
baseLocation = this.type === 'audio' ? 'audio/' : 'video/';
|
||||
fullLocation = this.baseStreamPath + baseLocation + encodeURIComponent(fileName) + '?subName=' + this.subscriptionName +
|
||||
'&subPlaylist=' + this.subPlaylist;
|
||||
}
|
||||
|
||||
@@ -280,17 +280,18 @@ export class PostsService implements CanActivate {
|
||||
return this.http.post(this.path + 'deletePlaylist', {playlistID: playlistID, type: type}, this.httpOptions);
|
||||
}
|
||||
|
||||
createSubscription(url, name, timerange = null, streamingOnly = false) {
|
||||
return this.http.post(this.path + 'subscribe', {url: url, name: name, timerange: timerange, streamingOnly: streamingOnly},
|
||||
this.httpOptions);
|
||||
createSubscription(url, name, timerange = null, streamingOnly = false, audioOnly = false, customArgs = null, customFileOutput = null) {
|
||||
return this.http.post(this.path + 'subscribe', {url: url, name: name, timerange: timerange, streamingOnly: streamingOnly,
|
||||
audioOnly: audioOnly, customArgs: customArgs, customFileOutput: customFileOutput}, this.httpOptions);
|
||||
}
|
||||
|
||||
unsubscribe(sub, deleteMode = false) {
|
||||
return this.http.post(this.path + 'unsubscribe', {sub: sub, deleteMode: deleteMode}, this.httpOptions)
|
||||
}
|
||||
|
||||
deleteSubscriptionFile(sub, file, deleteForever) {
|
||||
return this.http.post(this.path + 'deleteSubscriptionFile', {sub: sub, file: file, deleteForever: deleteForever}, this.httpOptions)
|
||||
deleteSubscriptionFile(sub, file, deleteForever, file_uid) {
|
||||
return this.http.post(this.path + 'deleteSubscriptionFile', {sub: sub, file: file, deleteForever: deleteForever,
|
||||
file_uid: file_uid}, this.httpOptions)
|
||||
}
|
||||
|
||||
getSubscription(id) {
|
||||
|
||||
@@ -71,14 +71,14 @@ export class SubscriptionFileCardComponent implements OnInit {
|
||||
}
|
||||
|
||||
deleteAndRedownload() {
|
||||
this.postsService.deleteSubscriptionFile(this.sub, this.file.id, false).subscribe(res => {
|
||||
this.postsService.deleteSubscriptionFile(this.sub, this.file.id, false, this.file.uid).subscribe(res => {
|
||||
this.reloadSubscription.emit(true);
|
||||
this.openSnackBar(`Successfully deleted file: '${this.file.id}'`, 'Dismiss.');
|
||||
});
|
||||
}
|
||||
|
||||
deleteForever() {
|
||||
this.postsService.deleteSubscriptionFile(this.sub, this.file.id, true).subscribe(res => {
|
||||
this.postsService.deleteSubscriptionFile(this.sub, this.file.id, true, this.file.uid).subscribe(res => {
|
||||
this.reloadSubscription.emit(true);
|
||||
this.openSnackBar(`Successfully deleted file: '${this.file.id}'`, 'Dismiss.');
|
||||
});
|
||||
|
||||
@@ -92,8 +92,9 @@ export class SubscriptionComponent implements OnInit {
|
||||
if (this.subscription.streamingOnly) {
|
||||
this.router.navigate(['/player', {name: name, url: url}]);
|
||||
} else {
|
||||
this.router.navigate(['/player', {fileNames: name, type: 'subscription', subscriptionName: this.subscription.name,
|
||||
subPlaylist: this.subscription.isPlaylist, uuid: this.postsService.user ? this.postsService.user.uid : null}]);
|
||||
this.router.navigate(['/player', {fileNames: 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}]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user