mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-03-07 12:00:01 +03:00
Added support for MongoDB Added tests relating to new DB system Category rules are now case insensitive Fixed playlist modification change state
398 lines
13 KiB
JavaScript
398 lines
13 KiB
JavaScript
const fs = require('fs-extra')
|
|
const path = require('path')
|
|
const config_api = require('./config');
|
|
const archiver = require('archiver');
|
|
|
|
const is_windows = process.platform === 'win32';
|
|
|
|
// replaces .webm with appropriate extension
|
|
function getTrueFileName(unfixed_path, type) {
|
|
let fixed_path = unfixed_path;
|
|
|
|
const new_ext = (type === 'audio' ? 'mp3' : 'mp4');
|
|
let unfixed_parts = unfixed_path.split('.');
|
|
const old_ext = unfixed_parts[unfixed_parts.length-1];
|
|
|
|
|
|
if (old_ext !== new_ext) {
|
|
unfixed_parts[unfixed_parts.length-1] = new_ext;
|
|
fixed_path = unfixed_parts.join('.');
|
|
}
|
|
return fixed_path;
|
|
}
|
|
|
|
async function getDownloadedFilesByType(basePath, type, full_metadata = false) {
|
|
// return empty array if the path doesn't exist
|
|
if (!(await fs.pathExists(basePath))) return [];
|
|
|
|
let files = [];
|
|
const ext = type === 'audio' ? 'mp3' : 'mp4';
|
|
var located_files = await recFindByExt(basePath, ext);
|
|
for (let i = 0; i < located_files.length; i++) {
|
|
let file = located_files[i];
|
|
var file_path = file.substring(basePath.includes('\\') ? basePath.length+1 : basePath.length, file.length);
|
|
|
|
var stats = await fs.stat(file);
|
|
|
|
var id = file_path.substring(0, file_path.length-4);
|
|
var jsonobj = await getJSONByType(type, id, basePath);
|
|
if (!jsonobj) continue;
|
|
if (full_metadata) {
|
|
jsonobj['id'] = id;
|
|
files.push(jsonobj);
|
|
continue;
|
|
}
|
|
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)}` : null;
|
|
|
|
var isaudio = type === 'audio';
|
|
var file_obj = new File(id, jsonobj.title, jsonobj.thumbnail, isaudio, jsonobj.duration, jsonobj.webpage_url, jsonobj.uploader,
|
|
stats.size, file, upload_date, jsonobj.description, jsonobj.view_count, jsonobj.height, jsonobj.abr);
|
|
files.push(file_obj);
|
|
}
|
|
return files;
|
|
}
|
|
|
|
async function createContainerZipFile(container_obj, container_file_objs) {
|
|
const container_files_to_download = [];
|
|
for (let i = 0; i < container_file_objs.length; i++) {
|
|
const container_file_obj = container_file_objs[i];
|
|
container_files_to_download.push(container_file_obj.path);
|
|
}
|
|
return await createZipFile(path.join('appdata', container_obj.name + '.zip'), container_files_to_download);
|
|
}
|
|
|
|
async function createZipFile(zip_file_path, file_paths) {
|
|
let output = fs.createWriteStream(zip_file_path);
|
|
|
|
var archive = archiver('zip', {
|
|
gzip: true,
|
|
zlib: { level: 9 } // Sets the compression level.
|
|
});
|
|
|
|
archive.on('error', function(err) {
|
|
logger.error(err);
|
|
throw err;
|
|
});
|
|
|
|
// pipe archive data to the output file
|
|
archive.pipe(output);
|
|
|
|
for (let file_path of file_paths) {
|
|
const file_name = path.parse(file_path).base;
|
|
archive.file(file_path, {name: file_name})
|
|
}
|
|
|
|
await archive.finalize();
|
|
|
|
// wait a tiny bit for the zip to reload in fs
|
|
await wait(100);
|
|
return zip_file_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 getJSON(file_path, type) {
|
|
const ext = type === 'audio' ? '.mp3' : '.mp4';
|
|
let obj = null;
|
|
var jsonPath = removeFileExtension(file_path) + '.info.json';
|
|
var alternateJsonPath = removeFileExtension(file_path) + `${ext}.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 getJSONByType(type, name, customPath, openReadPerms = false) {
|
|
return type === 'audio' ? getJSONMp3(name, customPath, openReadPerms) : getJSONMp4(name, customPath, openReadPerms)
|
|
}
|
|
|
|
function getDownloadedThumbnail(name, type, customPath = null) {
|
|
if (!customPath) customPath = type === 'audio' ? config_api.getConfigItem('ytdl_audio_folder_path') : config_api.getConfigItem('ytdl_video_folder_path');
|
|
|
|
let jpgPath = path.join(customPath, name + '.jpg');
|
|
let webpPath = path.join(customPath, name + '.webp');
|
|
let pngPath = path.join(customPath, name + '.png');
|
|
|
|
if (fs.existsSync(jpgPath))
|
|
return jpgPath;
|
|
else if (fs.existsSync(webpPath))
|
|
return webpPath;
|
|
else if (fs.existsSync(pngPath))
|
|
return pngPath;
|
|
else
|
|
return null;
|
|
}
|
|
|
|
function getDownloadedThumbnail2(file_path, type) {
|
|
const file_path_no_extension = removeFileExtension(file_path);
|
|
|
|
let jpgPath = file_path_no_extension + '.jpg';
|
|
let webpPath = file_path_no_extension + '.webp';
|
|
let pngPath = file_path_no_extension + '.png';
|
|
|
|
if (fs.existsSync(jpgPath))
|
|
return jpgPath;
|
|
else if (fs.existsSync(webpPath))
|
|
return webpPath;
|
|
else if (fs.existsSync(pngPath))
|
|
return pngPath;
|
|
else
|
|
return null;
|
|
}
|
|
|
|
function getExpectedFileSize(input_info_jsons) {
|
|
// treat single videos as arrays to have the file sizes checked/added to. makes the code cleaner
|
|
const info_jsons = Array.isArray(input_info_jsons) ? input_info_jsons : [input_info_jsons];
|
|
|
|
let expected_filesize = 0;
|
|
info_jsons.forEach(info_json => {
|
|
if (info_json['filesize']) {
|
|
expected_filesize += info_json['filesize'];
|
|
return;
|
|
}
|
|
const formats = info_json['format_id'].split('+');
|
|
let individual_expected_filesize = 0;
|
|
formats.forEach(format_id => {
|
|
info_json.formats.forEach(available_format => {
|
|
if (available_format.format_id === format_id && available_format.filesize) {
|
|
individual_expected_filesize += available_format.filesize;
|
|
}
|
|
});
|
|
});
|
|
expected_filesize += individual_expected_filesize;
|
|
});
|
|
|
|
return expected_filesize;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
function fixVideoMetadataPerms2(file_path, type) {
|
|
if (is_windows) return;
|
|
|
|
const ext = type === 'audio' ? '.mp3' : '.mp4';
|
|
|
|
const file_path_no_extension = removeFileExtension(file_path);
|
|
|
|
const files_to_fix = [
|
|
// JSONs
|
|
file_path_no_extension + '.info.json',
|
|
file_path_no_extension + ext + '.info.json',
|
|
// Thumbnails
|
|
file_path_no_extension + '.webp',
|
|
file_path_no_extension + '.jpg'
|
|
];
|
|
|
|
for (const file of files_to_fix) {
|
|
if (!fs.existsSync(file)) continue;
|
|
fs.chmodSync(file, 0o644);
|
|
}
|
|
}
|
|
|
|
function deleteJSONFile(name, type, customPath = null) {
|
|
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';
|
|
let json_path = path.join(customPath, name + '.info.json');
|
|
let alternate_json_path = path.join(customPath, name + ext + '.info.json');
|
|
|
|
if (fs.existsSync(json_path)) fs.unlinkSync(json_path);
|
|
if (fs.existsSync(alternate_json_path)) fs.unlinkSync(alternate_json_path);
|
|
}
|
|
|
|
function deleteJSONFile2(file_path, type) {
|
|
const ext = type === 'audio' ? '.mp3' : '.mp4';
|
|
|
|
const file_path_no_extension = removeFileExtension(file_path);
|
|
|
|
let json_path = file_path_no_extension + '.info.json';
|
|
let alternate_json_path = file_path_no_extension + ext + '.info.json';
|
|
|
|
if (fs.existsSync(json_path)) fs.unlinkSync(json_path);
|
|
if (fs.existsSync(alternate_json_path)) fs.unlinkSync(alternate_json_path);
|
|
}
|
|
|
|
async function removeIDFromArchive(archive_path, id) {
|
|
let data = await fs.readFile(archive_path, {encoding: 'utf-8'});
|
|
if (!data) {
|
|
logger.error('Archive could not be found.');
|
|
return;
|
|
}
|
|
|
|
let dataArray = data.split('\n'); // convert file data in an array
|
|
const searchKeyword = id; // we are looking for a line, contains, key word id in the file
|
|
let lastIndex = -1; // let say, we have not found the keyword
|
|
|
|
for (let index=0; index<dataArray.length; index++) {
|
|
if (dataArray[index].includes(searchKeyword)) { // check if a line contains the id keyword
|
|
lastIndex = index; // found a line includes a id keyword
|
|
break;
|
|
}
|
|
}
|
|
|
|
const line = dataArray.splice(lastIndex, 1); // remove the keyword id from the data Array
|
|
|
|
// UPDATE FILE WITH NEW DATA
|
|
const updatedData = dataArray.join('\n');
|
|
await fs.writeFile(archive_path, updatedData);
|
|
if (line) return line;
|
|
if (err) throw err;
|
|
}
|
|
|
|
function durationStringToNumber(dur_str) {
|
|
if (typeof dur_str === 'number') return dur_str;
|
|
let num_sum = 0;
|
|
const dur_str_parts = dur_str.split(':');
|
|
for (let i = dur_str_parts.length-1; i >= 0; i--) {
|
|
num_sum += parseInt(dur_str_parts[i])*(60**(dur_str_parts.length-1-i));
|
|
}
|
|
return num_sum;
|
|
}
|
|
|
|
function getMatchingCategoryFiles(category, files) {
|
|
return files && files.filter(file => file.category && file.category.uid === category.uid);
|
|
}
|
|
|
|
function addUIDsToCategory(category, files) {
|
|
const files_that_match = getMatchingCategoryFiles(category, files);
|
|
category['uids'] = files_that_match.map(file => file.uid);
|
|
return files_that_match;
|
|
}
|
|
|
|
async function recFindByExt(base,ext,files,result)
|
|
{
|
|
files = files || (await fs.readdir(base))
|
|
result = result || []
|
|
|
|
for (const file of files) {
|
|
var newbase = path.join(base,file)
|
|
if ( (await fs.stat(newbase)).isDirectory() )
|
|
{
|
|
result = await recFindByExt(newbase,ext,await fs.readdir(newbase),result)
|
|
}
|
|
else
|
|
{
|
|
if ( file.substr(-1*(ext.length+1)) == '.' + ext )
|
|
{
|
|
result.push(newbase)
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
function removeFileExtension(filename) {
|
|
const filename_parts = filename.split('.');
|
|
filename_parts.splice(filename_parts.length - 1);
|
|
return filename_parts.join('.');
|
|
}
|
|
|
|
/**
|
|
* setTimeout, but its a promise.
|
|
* @param {number} ms
|
|
*/
|
|
async function wait(ms) {
|
|
await new Promise(resolve => {
|
|
setTimeout(resolve, ms);
|
|
});
|
|
}
|
|
|
|
// objects
|
|
|
|
function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date, description, view_count, height, abr) {
|
|
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;
|
|
this.description = description;
|
|
this.view_count = view_count;
|
|
this.height = height;
|
|
this.abr = abr;
|
|
}
|
|
|
|
module.exports = {
|
|
getJSONMp3: getJSONMp3,
|
|
getJSONMp4: getJSONMp4,
|
|
getJSON: getJSON,
|
|
getTrueFileName: getTrueFileName,
|
|
getDownloadedThumbnail: getDownloadedThumbnail,
|
|
getDownloadedThumbnail2: getDownloadedThumbnail2,
|
|
getExpectedFileSize: getExpectedFileSize,
|
|
fixVideoMetadataPerms: fixVideoMetadataPerms,
|
|
fixVideoMetadataPerms2: fixVideoMetadataPerms2,
|
|
deleteJSONFile: deleteJSONFile,
|
|
deleteJSONFile2: deleteJSONFile2,
|
|
removeIDFromArchive, removeIDFromArchive,
|
|
getDownloadedFilesByType: getDownloadedFilesByType,
|
|
createContainerZipFile: createContainerZipFile,
|
|
durationStringToNumber: durationStringToNumber,
|
|
getMatchingCategoryFiles: getMatchingCategoryFiles,
|
|
addUIDsToCategory: addUIDsToCategory,
|
|
recFindByExt: recFindByExt,
|
|
removeFileExtension: removeFileExtension,
|
|
wait: wait,
|
|
File: File
|
|
}
|