You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
YoutubeDL-Material/backend/youtube-dl.js

160 lines
6.6 KiB
JavaScript

const fs = require('fs-extra');
const fetch = require('node-fetch');
const path = require('path');
const execa = require('execa');
const kill = require('tree-kill');
const logger = require('./logger');
const utils = require('./utils');
const CONSTS = require('./consts');
const config_api = require('./config.js');
const is_windows = process.platform === 'win32';
exports.youtubedl_forks = {
'youtube-dl': {
'download_url': 'https://github.com/ytdl-org/youtube-dl/releases/latest/download/youtube-dl',
'tags_url': 'https://api.github.com/repos/ytdl-org/youtube-dl/tags'
},
'youtube-dlc': {
'download_url': 'https://github.com/blackjack4494/yt-dlc/releases/latest/download/youtube-dlc',
'tags_url': 'https://api.github.com/repos/blackjack4494/yt-dlc/tags'
},
'yt-dlp': {
'download_url': 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp',
'tags_url': 'https://api.github.com/repos/yt-dlp/yt-dlp/tags'
}
}
exports.runYoutubeDL = async (url, args, customDownloadHandler = null) => {
const output_file_path = getYoutubeDLPath();
if (!fs.existsSync(output_file_path)) await exports.checkForYoutubeDLUpdate();
let callback = null;
let child_process = null;
if (customDownloadHandler) {
callback = runYoutubeDLCustom(url, args, customDownloadHandler);
} else {
({callback, child_process} = await runYoutubeDLProcess(url, args));
}
return {child_process, callback};
}
// Run youtube-dl directly (not cancellable)
const runYoutubeDLCustom = async (url, args, customDownloadHandler) => {
const downloadHandler = customDownloadHandler;
Improved and fixed tests Skipped tests just meant for manual testing mocha now used prod mode Added automated tests for PRs Fixed backupdb and youtube-dl tests in gh actions Fixed downloader tests Removed erroneous logs in tests Updated mocha.yml Removed last console.log in tests Increased timeout for download video test Automated tests now force stop after tests complete Replaced download test url with shorter video Skip download test... for now increased download test threshold added more timing information to tests unskip download test Bump docker/setup-buildx-action from 1 to 2 in /.github/workflows Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 1 to 2. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v1...v2) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Bump actions/setup-node from 2 to 3 in /.github/workflows Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2 to 3. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Bump actions/upload-artifact from 1 to 3 in /.github/workflows Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 1 to 3. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v1...v3) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Bump docker/login-action from 1 to 2 in /.github/workflows Bumps [docker/login-action](https://github.com/docker/login-action) from 1 to 2. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v1...v2) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Removed additional download button for paused download Combined output json processing from youtube-dl between subscriptions and one-off downloads Added workaround for missing video in playlist to one-off downloads Fixed issue of case where output is empty Download video code cleanup Fixed download video test by overriding youtube-dl download method with custom method Adds ffmpeg to automated tests Removed test mp3/mp4 file from repo
2 years ago
return new Promise(resolve => {
downloadHandler(url, args, {maxBuffer: Infinity}, async function(err, output) {
Improved and fixed tests Skipped tests just meant for manual testing mocha now used prod mode Added automated tests for PRs Fixed backupdb and youtube-dl tests in gh actions Fixed downloader tests Removed erroneous logs in tests Updated mocha.yml Removed last console.log in tests Increased timeout for download video test Automated tests now force stop after tests complete Replaced download test url with shorter video Skip download test... for now increased download test threshold added more timing information to tests unskip download test Bump docker/setup-buildx-action from 1 to 2 in /.github/workflows Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 1 to 2. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v1...v2) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Bump actions/setup-node from 2 to 3 in /.github/workflows Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2 to 3. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Bump actions/upload-artifact from 1 to 3 in /.github/workflows Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 1 to 3. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v1...v3) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Bump docker/login-action from 1 to 2 in /.github/workflows Bumps [docker/login-action](https://github.com/docker/login-action) from 1 to 2. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v1...v2) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Removed additional download button for paused download Combined output json processing from youtube-dl between subscriptions and one-off downloads Added workaround for missing video in playlist to one-off downloads Fixed issue of case where output is empty Download video code cleanup Fixed download video test by overriding youtube-dl download method with custom method Adds ffmpeg to automated tests Removed test mp3/mp4 file from repo
2 years ago
const parsed_output = utils.parseOutputJSON(output, err);
resolve({parsed_output, err});
});
});
}
// Run youtube-dl in a subprocess (cancellable)
const runYoutubeDLProcess = async (url, args, youtubedl_fork = config_api.getConfigItem('ytdl_default_downloader')) => {
const youtubedl_path = getYoutubeDLPath(youtubedl_fork);
const binary_exists = fs.existsSync(youtubedl_path);
if (!binary_exists) {
const err = `Could not find path for ${youtubedl_fork} at ${youtubedl_path}`;
logger.error(err);
return;
}
const child_process = execa(getYoutubeDLPath(youtubedl_fork), [url, ...args], {maxBuffer: Infinity});
const callback = new Promise(async resolve => {
try {
const {stdout, stderr} = await child_process;
const parsed_output = utils.parseOutputJSON(stdout.trim().split(/\r?\n/), stderr);
resolve({parsed_output, err: stderr});
} catch (e) {
resolve({parsed_output: null, err: e})
}
});
return {child_process, callback}
}
function getYoutubeDLPath(youtubedl_fork = config_api.getConfigItem('ytdl_default_downloader')) {
const binary_file_name = youtubedl_fork + (is_windows ? '.exe' : '');
const binary_path = path.join('appdata', 'bin', binary_file_name);
return binary_path;
}
exports.killYoutubeDLProcess = async (child_process) => {
kill(child_process.pid, 'SIGKILL');
}
exports.checkForYoutubeDLUpdate = async () => {
const selected_fork = config_api.getConfigItem('ytdl_default_downloader');
const output_file_path = getYoutubeDLPath();
// get current version
let current_app_details_exists = fs.existsSync(CONSTS.DETAILS_BIN_PATH);
if (!current_app_details_exists) {
logger.warn(`Failed to get youtube-dl binary details at location '${CONSTS.DETAILS_BIN_PATH}'. Generating file...`);
updateDetailsJSON(CONSTS.OUTDATED_YOUTUBEDL_VERSION, selected_fork, output_file_path);
}
const current_app_details = JSON.parse(fs.readFileSync(CONSTS.DETAILS_BIN_PATH));
const current_version = current_app_details['version'];
const current_fork = current_app_details['downloader'];
const latest_version = await exports.getLatestUpdateVersion(selected_fork);
// if the binary does not exist, or default_downloader doesn't match existing fork, or if the fork has been updated, redownload
// TODO: don't redownload if fork already exists
if (!fs.existsSync(output_file_path) || current_fork !== selected_fork || !current_version || current_version !== latest_version) {
logger.warn(`Updating ${selected_fork} binary to '${output_file_path}', downloading...`);
await exports.updateYoutubeDL(latest_version);
}
}
exports.updateYoutubeDL = async (latest_update_version, custom_output_path = null) => {
await fs.ensureDir(path.join('appdata', 'bin'));
const default_downloader = config_api.getConfigItem('ytdl_default_downloader');
await downloadLatestYoutubeDLBinaryGeneric(default_downloader, latest_update_version, custom_output_path);
}
async function downloadLatestYoutubeDLBinaryGeneric(youtubedl_fork, new_version, custom_output_path = null) {
const file_ext = is_windows ? '.exe' : '';
// build the URL
const download_url = `${exports.youtubedl_forks[youtubedl_fork]['download_url']}${file_ext}`;
const output_path = custom_output_path || getYoutubeDLPath(youtubedl_fork);
await utils.fetchFile(download_url, output_path, `${youtubedl_fork} ${new_version}`);
fs.chmod(output_path, 0o777);
updateDetailsJSON(new_version, youtubedl_fork, output_path);
}
exports.getLatestUpdateVersion = async (youtubedl_fork) => {
const tags_url = exports.youtubedl_forks[youtubedl_fork]['tags_url'];
return new Promise(resolve => {
fetch(tags_url, {method: 'Get'})
.then(async res => res.json())
.then(async (json) => {
if (!json || !json[0]) {
logger.error(`Failed to check ${youtubedl_fork} version for an update.`)
resolve(null);
return;
}
const latest_update_version = json[0]['name'];
resolve(latest_update_version);
})
.catch(err => {
logger.error(`Failed to check ${youtubedl_fork} version for an update.`)
logger.error(err);
resolve(null);
});
});
}
function updateDetailsJSON(new_version, fork, output_path) {
const file_ext = is_windows ? '.exe' : '';
const details_json = fs.existsSync(CONSTS.DETAILS_BIN_PATH) ? fs.readJSONSync(CONSTS.DETAILS_BIN_PATH) : {};
if (!details_json[fork]) details_json[fork] = {};
const fork_json = details_json[fork];
fork_json['version'] = new_version;
fork_json['downloader'] = fork;
fork_json['path'] = output_path; // unused
fork_json['exec'] = fork + file_ext; // unused
fs.writeJSONSync(CONSTS.DETAILS_BIN_PATH, details_json);
}