I'm working on a YouTube video downloader similar to Y2Mate or SaveFrom, using Node.js with ytdl and FFmpeg. The problem is that when I download any video, I only get formats up to 360p. I want to support 720p and 1080p, but it's not working.
Can anyone help me figure this out? I'd really appreciate it. This YouTube downloader is only for educational purposes, as I am a developer and will not use it for illegal activities.
Youtube.js
const express = require("express");
const router = express.Router();
const { exec } = require("child_process");
const path = require("path");
const YTDLP = path.join(__dirname, "..", "yt-dlp.exe");
const FFMPEG = path.join(__dirname, "..", "ffmpeg.exe");
// --------------------------
// GET VIDEO INFO (FORMATS)
// --------------------------
router.post("/video-info", (req, res) => {
const { url } = req.body;
if (!url) {
return res.json({ success: false, error: "No URL provided" });
}
const cmd = `"${YTDLP}" --no-warnings -J "${url}"`;
exec(cmd, (err, stdout) => {
if (err) return res.json({ success: false, error: "Failed to get info" });
try {
const info = JSON.parse(stdout);
const title = info.title;
const thumbnail = info.thumbnail;
const formats = [];
const audioFormats = [];
info.formats.forEach((f) => {
if (!f.url) return;
// AUDIO
if (f.vcodec === "none" && f.acodec !== "none") {
audioFormats.push({
itag: f.format_id,
quality: "High Quality",
ext: "mp3",
url: f.url
});
}
// ONLY MP4 VIDEO QUALITIES
if (f.ext === "mp4" && f.vcodec !== "none" && f.acodec !== "none") {
formats.push({
itag: f.format_id,
quality: f.format_note || f.resolution,
ext: "mp4",
size: f.filesize || null,
url: f.url
});
}
});
// SORT video formats by quality
const qualityOrder = ["2160p", "1440p", "1080p", "720p", "480p", "360p", "240p", "144p"];
const sortedFormats = qualityOrder
.map(q => formats.find(f => (f.quality + "").includes(q)))
.filter(Boolean); // remove null
res.json({
success: true,
title,
thumbnail,
formats: sortedFormats,
mp3: audioFormats[0] || null
});
} catch (e) {
console.error(e);
res.json({ success: false, error: "Parse error" });
}
});
});
// --------------------------
// DOWNLOAD ROUTE
// --------------------------
router.get("/download", (req, res) => {
const { url, itag, title } = req.query;
const safeName = title.replace(/[\/\\:*?"<>|]/g, "");
const output = `${safeName}.mp4`;
const cmd = `"${YTDLP}" -f ${itag}+bestaudio --merge-output-format mp4 -o "${output}" "${url}"`;
exec(cmd, () => {
res.download(output, () => {
try {
fs.unlinkSync(output);
} catch {}
});
});
});
module.exports = router;
Index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>YouTube Downloader – Y2Mate Style</title>
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
background: #f5f5f5;
}
header {
padding: 25px;
background: #ffffff;
box-shadow: 0px 2px 5px rgba(0,0,0,0.1);
text-align: center;
}
h1 {
margin: 0;
font-size: 30px;
color: #333;
}
.container {
max-width: 720px;
margin: 40px auto;
background: white;
padding: 25px;
border-radius: 10px;
box-shadow: 0 3px 10px rgba(0,0,0,0.1);
}
.input-group {
display: flex;
gap: 10px;
}
input {
flex: 1;
padding: 12px;
border: 2px solid #ddd;
border-radius: 6px;
font-size: 16px;
}
button {
padding: 12px 18px;
background: #4caf50;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
font-weight: bold;
}
button:hover {
background: #43a047;
}
.video-card {
margin-top: 25px;
display: flex;
gap: 15px;
background: #fff;
padding: 15px;
border-radius: 10px;
box-shadow: 0px 2px 8px rgba(0,0,0,0.1);
}
.video-card img {
width: 160px;
height: auto;
border-radius: 6px;
}
table {
width: 100%;
margin-top: 20px;
border-collapse: collapse;
}
table th, table td {
border: 1px solid #eee;
padding: 12px;
text-align: left;
}
table th {
background: #fafafa;
}
.download-btn {
background: #1e88e5;
padding: 8px 14px;
color: white;
border-radius: 6px;
text-decoration: none;
}
.download-btn:hover {
background: #1565c0;
}
#progressBar {
position: fixed;
top: 0;
left: 0;
height: 4px;
width: 0%;
background: #4caf50;
z-index: 9999;
transition: width 0.3s ease;
}
</style>
</head>
<body>
<div id="progressBar"></div>
<header>
<h1>YouTube Downloader</h1>
</header>
<div class="container">
<div class="input-group">
<input type="text" id="videoURL" placeholder="Paste YouTube link here..." />
<button onclick="getInfo()">Start</button>
</div>
<div id="videoDetails"></div>
<div id="formatList"></div>
</div>
<script>
const API = "http://localhost:9000/api/video-info";
const DL_API = "http://localhost:9000/api/download";
function startProgress() {
const bar = document.getElementById("progressBar");
bar.style.width = "0%";
// Animate progress bar to 80%
setTimeout(() => (bar.style.width = "40%"), 100);
setTimeout(() => (bar.style.width = "70%"), 500);
}
function finishProgress() {
const bar = document.getElementById("progressBar");
bar.style.width = "100%";
setTimeout(() => {
bar.style.width = "0%";
}, 300); // hide smoothly
}
function showLoader() {
document.getElementById("videoDetails").innerHTML = `
<div style="width:100%; text-align:center; margin-top:30px;">
<div class="loader"></div>
<p style="color:#555; margin-top:10px;">Extracting data…</p>
</div>
`;
}
async function getInfo() {
const url = document.getElementById("videoURL").value.trim();
if (!url) return alert("Enter a valid YouTube link!");
showLoader();
startProgress(); // 🔥 Start progress bar
try {
const res = await fetch(API, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url })
});
const data = await res.json();
if (!data.success) {
document.getElementById("videoDetails").innerHTML =
"<p style='color:red;'>Failed to fetch video info.</p>";
finishProgress();
return;
}
renderVideo(data);
renderFormats(data);
finishProgress(); // 🔥 Successful load
} catch (err) {
document.getElementById("videoDetails").innerHTML =
"<p style='color:red;'>Server error. Try again.</p>";
finishProgress();
}
}
function renderVideo(data) {
document.getElementById("videoDetails").innerHTML = `
<div class="video-card">
<img src="${data.thumbnail}" alt="thumbnail">
<div>
<h2>${data.title}</h2>
</div>
</div>
`;
}
function renderFormats(data) {
let html = `
<table>
<tr>
<th>Quality</th>
<th>Format</th>
<th>Action</th>
</tr>
`;
data.formats.forEach(f => {
html += `
<tr>
<td>${f.quality}</td>
<td>${f.ext.toUpperCase()}</td>
<td>
<a class="download-btn"
href="http://localhost:9000/api/download?url=${encodeURIComponent(document.getElementById("videoURL").value)}&itag=${f.itag}&title=${data.title}">
Download
</a>
</td>
</tr>
`;
});
if (data.mp3) {
html += `
<tr>
<td>MP3 (Audio)</td>
<td>MP3</td>
<td>
<a class="download-btn"
href="http://localhost:9000/api/download?url=${encodeURIComponent(document.getElementById("videoURL").value)}&itag=${data.mp3.itag}&title=${data.title}">
Download MP3
</a>
</td>
</tr>
`;
}
html += `</table>`;
document.getElementById("formatList").innerHTML = html;
}
</script>
</body>
</html>
yt-dlp.exe) does the same when called from the command line, or you're not passing the correct parameters to it.