yt-dlx
Version:
Effortless Audio-Video Downloader And Streamer!
234 lines • 11.2 kB
JavaScript
import * as fs from "fs";
import colors from "colors";
import * as path from "path";
import { z, ZodError } from "zod";
import ffmpeg from "fluent-ffmpeg";
import Tuber from "../../utils/Agent";
import { locator } from "../../utils/locator";
import { PassThrough } from "stream";
function formatTime(seconds) {
if (!isFinite(seconds) || isNaN(seconds))
return "00h 00m 00s";
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
return `${hours.toString().padStart(2, "0")}h ${minutes.toString().padStart(2, "0")}m ${secs.toString().padStart(2, "0")}s`;
}
function calculateETA(startTime, percent) {
const currentTime = new Date();
const elapsedTime = (currentTime.getTime() - startTime.getTime()) / 1000;
if (percent <= 0)
return NaN;
const totalTimeEstimate = (elapsedTime / percent) * 100;
const remainingTime = totalTimeEstimate - elapsedTime;
return remainingTime;
}
function progbar({ percent, timemark, startTime }) {
let displayPercent = isNaN(percent || 0) ? 0 : percent || 0;
displayPercent = Math.min(Math.max(displayPercent, 0), 100);
const colorFn = displayPercent < 25 ? colors.red : displayPercent < 50 ? colors.yellow : colors.green;
const width = Math.floor((process.stdout.columns || 80) / 4);
const scomp = Math.round((width * displayPercent) / 100);
const progb = colorFn("━").repeat(scomp) + colorFn(" ").repeat(width - scomp);
const etaSeconds = calculateETA(startTime, displayPercent);
const etaFormatted = formatTime(etaSeconds);
process.stdout.write(`\r${colorFn("@prog:")} ${progb} ${colorFn("| @percent:")} ${displayPercent.toFixed(2)}% ${colorFn("| @timemark:")} ${timemark} ${colorFn("| @eta:")} ${etaFormatted}`);
}
const ZodSchema = z.object({
query: z.string().min(2),
output: z.string().optional(),
useTor: z.boolean().optional(),
stream: z.boolean().optional(),
verbose: z.boolean().optional(),
metadata: z.boolean().optional(),
showProgress: z.boolean().optional(),
filter: z.enum(["invert", "rotate90", "rotate270", "grayscale", "rotate180", "flipVertical", "flipHorizontal"]).optional(),
resolution: z.string().regex(/^\d+p(\d+)?$/),
});
export default async function VideoCustom({ query, output, useTor, stream, filter, metadata, verbose, resolution, showProgress, }) {
try {
ZodSchema.parse({ query, output, useTor, stream, filter, metadata, verbose, resolution, showProgress });
if (metadata && (stream || output || filter || showProgress)) {
throw new Error(`${colors.red("@error:")} The 'metadata' parameter cannot be used with 'stream', 'output', 'filter', or 'showProgress'.`);
}
if (stream && output) {
throw new Error(`${colors.red("@error:")} The 'stream' parameter cannot be used with 'output'.`);
}
const engineData = await Tuber({ query, verbose, useTor });
if (!engineData) {
throw new Error(`${colors.red("@error:")} Unable to retrieve a response from the engine.`);
}
if (!engineData.metaData) {
throw new Error(`${colors.red("@error:")} Metadata not found in the engine response.`);
}
if (metadata) {
return {
metadata: {
metaData: engineData.metaData,
VideoLowF: engineData.VideoLowF,
VideoHighF: engineData.VideoHighF,
VideoLowHDR: engineData.VideoLowHDR,
VideoHighHDR: engineData.VideoHighHDR,
ManifestLow: engineData.ManifestLow,
ManifestHigh: engineData.ManifestHigh,
filename: `yt-dlx_VideoCustom_${resolution}_${filter ? filter + "_" : ""}${engineData.metaData.title?.replace(/[^a-zA-Z0-9_]+/g, "_") || "video"}.mp4`,
},
};
}
const title = engineData.metaData.title?.replace(/[^a-zA-Z0-9_]+/g, "_") || "video";
const folder = output ? output : process.cwd();
if (!stream && !fs.existsSync(folder)) {
try {
fs.mkdirSync(folder, { recursive: true });
}
catch (mkdirError) {
throw new Error(`${colors.red("@error:")} Failed to create output directory: ${mkdirError.message}`);
}
}
const instance = ffmpeg();
try {
const paths = await locator();
if (!paths.ffmpeg) {
throw new Error(`${colors.red("@error:")} ffmpeg executable not found.`);
}
if (!paths.ffprobe) {
throw new Error(`${colors.red("@error:")} ffprobe executable not found.`);
}
instance.setFfmpegPath(paths.ffmpeg);
instance.setFfprobePath(paths.ffprobe);
}
catch (locatorError) {
throw new Error(`${colors.red("@error:")} Failed to locate ffmpeg or ffprobe: ${locatorError.message}`);
}
const resolutionRegex = /(\d+)p(\d+)?/;
const resolutionMatch = resolution.match(resolutionRegex);
const targetHeight = resolutionMatch ? parseInt(resolutionMatch[1], 10) : null;
const targetFps = resolutionMatch && resolutionMatch[2] ? parseInt(resolutionMatch[2], 10) : null;
const vdata = engineData.allFormats?.find((i) => {
const height = i.height;
const fps = i.fps;
const vcodec = i.vcodec;
let heightMatches = height === targetHeight;
let fpsMatches = targetFps === null || fps === targetFps;
return heightMatches && fpsMatches && vcodec !== "none";
});
if (!vdata) {
}
if (!vdata.url) {
throw new Error(`${colors.red("@error:")} Video URL not found for resolution: ${resolution}`);
}
instance.addInput(vdata.url.toString());
instance.withOutputFormat("mp4");
const filterMap = {
grayscale: ["colorchannelmixer=.3:.4:.3:0:.3:.4:.3:0:.3:.4:.3"],
invert: ["negate"],
rotate90: ["rotate=PI/2"],
rotate180: ["rotate=PI"],
rotate270: ["rotate=3*PI/2"],
flipHorizontal: ["hflip"],
flipVertical: ["vflip"],
};
if (filter && filterMap[filter]) {
instance.withVideoFilter(filterMap[filter]);
}
else {
instance.outputOptions("-c copy");
}
let processStartTime;
if (showProgress) {
instance.on("start", () => {
processStartTime = new Date();
});
instance.on("progress", progress => {
if (processStartTime) {
progbar({ ...progress, percent: progress.percent !== undefined ? progress.percent : 0, startTime: processStartTime });
}
});
}
if (stream) {
const passthroughStream = new PassThrough();
const filenameBase = `yt-dlx_VideoCustom_${resolution}_`;
let filename = `${filenameBase}${filter ? filter + "_" : ""}${title}.mp4`;
passthroughStream.filename = filename;
instance.on("start", command => {
if (verbose)
console.log(colors.green("@info:"), "FFmpeg stream started:", command);
});
instance.pipe(passthroughStream, { end: true });
instance.on("end", () => {
if (verbose)
console.log(colors.green("@info:"), "FFmpeg streaming finished.");
if (showProgress)
process.stdout.write("\n");
});
instance.on("error", (error, stdout, stderr) => {
const errorMessage = `${colors.red("@error:")} FFmpeg stream error: ${error?.message}`;
console.error(errorMessage, "\nstdout:", stdout, "\nstderr:", stderr);
passthroughStream.emit("error", new Error(errorMessage));
passthroughStream.destroy(new Error(errorMessage));
if (showProgress)
process.stdout.write("\n");
});
instance.run();
if (verbose)
console.log(colors.green("@info:"), "❣️ Thank you for using yt-dlx. Consider 🌟starring the GitHub repo https://github.com/yt-dlx.");
return { stream: passthroughStream, filename: filename };
}
else {
const filenameBase = `yt-dlx_VideoCustom_${resolution}_`;
let filename = `${filenameBase}${filter ? filter + "_" : ""}${title}.mp4`;
const outputPath = path.join(folder, filename);
instance.output(outputPath);
await new Promise((resolve, reject) => {
instance.on("start", command => {
if (verbose)
console.log(colors.green("@info:"), "FFmpeg download started:", command);
if (showProgress)
processStartTime = new Date();
});
instance.on("progress", progress => {
if (showProgress && processStartTime) {
progbar({ ...progress, percent: progress.percent !== undefined ? progress.percent : 0, startTime: processStartTime });
}
});
instance.on("end", () => {
if (verbose)
console.log(colors.green("@info:"), "FFmpeg download finished.");
if (showProgress)
process.stdout.write("\n");
resolve();
});
instance.on("error", (error, stdout, stderr) => {
const errorMessage = `${colors.red("@error:")} FFmpeg download error: ${error?.message}`;
console.error(errorMessage, "\nstdout:", stdout, "\nstderr:", stderr);
if (showProgress)
process.stdout.write("\n");
reject(new Error(errorMessage));
});
instance.run();
});
if (verbose)
console.log(colors.green("@info:"), "❣️ Thank you for using yt-dlx. Consider 🌟starring the GitHub repo https://github.com/yt-dlx.");
return { outputPath };
}
}
catch (error) {
if (error instanceof ZodError) {
const errorMessage = `${colors.red("@error:")} Argument validation failed: ${error.errors.map(e => `${e.path.join(".")}: ${e.message}`).join(", ")}`;
console.error(errorMessage);
throw new Error(errorMessage);
}
else if (error instanceof Error) {
console.error(error.message);
throw error;
}
else {
const unexpectedError = `${colors.red("@error:")} An unexpected error occurred: ${String(error)}`;
console.error(unexpectedError);
throw new Error(unexpectedError);
}
}
finally {
}
}
//# sourceMappingURL=Custom.js.map