-

-
${v.title}
-
Duration : ${durationStr}
-
URL
-
-
+
+
+

+
${durationStr}
+
+
+
${v.title}
+ ${v.uploader ? `
${v.uploader}
` : ''}
+
+
`;
});
// Gestion du bouton copier
listDiv.addEventListener("click", (event) => {
- if (event.target.classList.contains("copy-btn")) {
- const btn = event.target;
+ if (event.target.classList.contains("copy-btn") || event.target.closest(".copy-btn")) {
+ const btn = event.target.closest(".copy-btn") || event.target;
if (btn.disabled) return;
btn.disabled = true;
const url = btn.dataset.url;
navigator.clipboard.writeText(url)
.then(() => {
- const original = btn.textContent;
+ const original = btn.innerHTML;
btn.style.opacity = 0;
btn.style.transform = "scale(0.7)";
setTimeout(() => {
- btn.textContent = "✅";
+ btn.innerHTML = `
`;
btn.style.opacity = 1;
btn.style.transform = "scale(1)";
@@ -129,7 +166,7 @@ async function init() {
btn.style.transform = "scale(0.7)";
setTimeout(() => {
- btn.textContent = original;
+ btn.innerHTML = original;
btn.style.opacity = 1;
btn.style.transform = "scale(1)";
btn.disabled = false;
@@ -140,10 +177,12 @@ async function init() {
}, 300);
})
.catch(() => {
- const original = btn.textContent;
- btn.textContent = "❌";
+ const original = btn.innerHTML;
+ btn.innerHTML = `
`;
setTimeout(() => {
- btn.textContent = original;
+ btn.innerHTML = original;
btn.disabled = false;
}, 1500);
});
diff --git a/public/styles/layout/videoinfo.css b/public/styles/layout/videoinfo.css
index 743dc00..79ecb98 100644
--- a/public/styles/layout/videoinfo.css
+++ b/public/styles/layout/videoinfo.css
@@ -69,80 +69,293 @@
/* --- Playlist Mode --- */
#videoInfo.playlist-mode {
- max-width: 900px;
+ max-width: 95%;
}
-/* Style du bloc "Playlist détectée" */
-#videoInfo.playlist-mode > p {
- text-align: center;
- font-size: 1.1rem;
+/* Playlist Header */
+.playlist-header {
+ display: flex;
+ flex-direction: column;
+ padding: 2rem;
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.03));
+ border-radius: 16px;
+ margin-bottom: 2rem;
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ backdrop-filter: blur(10px);
+}
+
+.playlist-badge {
+ display: inline-flex;
+ align-items: center;
+ width: fit-content;
+ padding: 0.4rem 0.9rem;
+ background: rgba(255, 255, 255, 0.1);
+ border: 1px solid rgba(255, 255, 255, 0.15);
+ border-radius: 6px;
+ font-size: 0.8rem;
font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ color: var(--video-info-text-color);
+ margin-bottom: 1rem;
+}
+
+.playlist-info {
+ flex: 1;
+}
+
+.playlist-title {
+ font-size: 1.9rem;
+ margin: 0 0 0.6rem 0;
+ color: var(--video-info-heading-color);
+ font-weight: 700;
+ line-height: 1.2;
+}
+
+.playlist-meta {
+ display: flex;
+ gap: 1.5rem;
+ font-size: 0.95rem;
+ color: var(--video-info-text-color);
+ opacity: 0.85;
+ flex-wrap: wrap;
+}
+
+.playlist-count,
+.playlist-channel {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.4rem 0.8rem;
+ background: rgba(255, 255, 255, 0.05);
+ border-radius: 8px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ font-weight: 500;
+}
+
+.playlist-count svg,
+.playlist-channel svg {
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
}
/* Layout liste des vidéos */
#playlistVideos {
margin-top: 1em;
display: grid;
- grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
- gap: 18px;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 1.5rem;
}
-/* Un item de playlist */
-#playlistVideos > div {
+/* Carte vidéo playlist */
+.playlist-video-card {
background: var(--playlist-background-color);
- border-radius: 10px;
- padding: 10px;
- box-shadow: 0 4px 12px rgba(0,0,0,0.15);
- text-align: center;
- transition: transform 0.2s ease;
-}
-
-#playlistVideos > div p {
+ border-radius: 14px;
+ overflow: hidden;
+ box-shadow: 0 2px 12px rgba(0,0,0,0.12);
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
- align-items: center;
- justify-content: center;
- flex-wrap: wrap;
+ flex-direction: column;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ position: relative;
}
-#videoInfo.playlist-mode {
- max-width: 95%;
+.playlist-video-card::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 3px;
+ background: linear-gradient(90deg, var(--video-info-link-color), transparent);
+ opacity: 0;
+ transition: opacity 0.25s ease;
}
-#playlistVideos > div:hover {
+.playlist-video-card:hover {
+ transform: translateY(-6px);
+ box-shadow: 0 12px 32px rgba(0,0,0,0.25);
+ border-color: rgba(255, 255, 255, 0.15);
+}
+
+.playlist-video-card:hover::before {
+ opacity: 1;
+}
+
+/* Thumbnail wrapper avec durée */
+.video-thumbnail-wrapper {
+ position: relative;
+ width: 100%;
+ aspect-ratio: 16 / 9;
+ overflow: hidden;
+ background: #000;
+}
+
+.video-thumbnail {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+ transition: transform 0.3s ease;
+}
+
+.playlist-video-card:hover .video-thumbnail {
transform: scale(1.05);
}
-#playlistVideos img {
- width: 100%;
+.video-duration {
+ position: absolute;
+ bottom: 8px;
+ right: 8px;
+ background: rgba(0, 0, 0, 0.9);
+ color: white;
+ padding: 4px 8px;
+ border-radius: 6px;
+ font-size: 0.85rem;
+ font-weight: 700;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', monospace;
+ letter-spacing: 0.5px;
+ backdrop-filter: blur(4px);
+}
+
+/* Contenu vidéo */
+.video-content {
+ padding: 1.2rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.6rem;
+ flex: 1;
+ background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.02));
+}
+
+.video-title {
+ font-size: 1rem;
+ font-weight: 600;
+ margin: 0;
+ color: var(--video-info-heading-color);
+ line-height: 1.4;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ min-height: 2.8em;
+ transition: color 0.2s ease;
+}
+
+.playlist-video-card:hover .video-title {
+ color: var(--video-info-link-color);
+}
+
+.video-uploader {
+ font-size: 0.9rem;
+ color: var(--video-info-text-color);
+ opacity: 0.7;
+ margin: 0;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* Actions vidéo */
+.video-actions {
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+ margin-top: auto;
+ padding-top: 0.8rem;
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
+}
+
+.video-link {
+ flex: 1;
+ text-decoration: none;
+ color: var(--video-info-link-color);
+ font-size: 0.9rem;
+ padding: 0.5rem 0.9rem;
border-radius: 8px;
- margin-bottom: 8px;
+ background: rgba(255, 255, 255, 0.06);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ transition: all 0.2s ease;
+ text-align: center;
+ font-weight: 500;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.4rem;
+}
+
+.video-link svg {
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
+}
+
+.video-link:hover {
+ background: rgba(255, 255, 255, 0.12);
+ border-color: rgba(255, 255, 255, 0.15);
+ text-decoration: none;
+ transform: translateY(-1px);
}
.copy-btn {
- all: unset;
- background: transparent;
- border: none;
+ background: rgba(255, 255, 255, 0.06);
+ border: 1px solid rgba(255, 255, 255, 0.08);
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
- padding: 4px;
- vertical-align: middle;
- transition: transform 0.1s ease, color 0.1s ease, opacity 0.15s ease;
- font-size: 1em;
+ padding: 0.5rem;
+ border-radius: 8px;
+ transition: all 0.2s ease;
+ width: 40px;
+ height: 40px;
+ flex-shrink: 0;
}
-#playlistVideos > div p a{
- text-decoration: none;
- color: var(--video-info-link-color);
+.copy-btn svg {
+ width: 18px;
+ height: 18px;
+ color: var(--video-info-text-color);
}
-#playlistVideos > div p a:hover{
- text-decoration: underline;
+.copy-btn:hover {
+ background: rgba(255, 255, 255, 0.12);
+ border-color: rgba(255, 255, 255, 0.15);
+ transform: translateY(-1px);
+}
+
+.copy-btn:active {
+ transform: scale(0.95);
+}
+
+.copy-btn:disabled {
+ cursor: not-allowed;
+ opacity: 0.6;
}
/* Responsive */
+@media (max-width: 768px) {
+ #playlistVideos {
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+ gap: 1rem;
+ }
+
+ .playlist-header {
+ padding: 1.5rem;
+ }
+
+ .playlist-title {
+ font-size: 1.5rem;
+ }
+
+ .playlist-meta {
+ gap: 0.8rem;
+ }
+}
+
@media (max-width: 480px) {
#videoInfo {
padding: 1em;
@@ -152,4 +365,21 @@
#videoInfo h3 {
font-size: 1.4rem;
}
+
+ #playlistVideos {
+ grid-template-columns: 1fr;
+ }
+
+ .playlist-header {
+ padding: 1.2rem;
+ }
+
+ .playlist-title {
+ font-size: 1.3rem;
+ }
+
+ .playlist-badge {
+ font-size: 0.75rem;
+ padding: 0.4rem 0.8rem;
+ }
}
diff --git a/server/controller/download.controller.js b/server/controller/download.controller.js
index 597b290..f875859 100644
--- a/server/controller/download.controller.js
+++ b/server/controller/download.controller.js
@@ -16,10 +16,14 @@ async function downloadController(req, res) {
};
if (!options.url || !isValidUrl(options.url)) return res.status(400).send("❌ Invalid URL !");
- if (options.outputFolder && !isSafePath(options.outputFolder)) return res.status(400).send("❌ Save Path Not Allowed.");
+ if (options.outputFolder && !isSafePath(options.outputFolder)) {
+ logger.warn(`Unsafe download path rejected: ${options.outputFolder}`);
+ return res.status(400).send("❌ Save Path Not Allowed.");
+ }
- const filePath = await fetchDownload(options, listeners, speedListeners);
- notifyDownloadFinished(filePath);
+ // Get output folder when the download is finished
+ const outputFolder = await fetchDownload(options, listeners, speedListeners);
+ notifyDownloadFinished(outputFolder);
res.send("✅ Download Done !");
} catch (err) {
diff --git a/server/controller/info.controller.js b/server/controller/info.controller.js
index 7002cbf..81837a9 100644
--- a/server/controller/info.controller.js
+++ b/server/controller/info.controller.js
@@ -13,7 +13,7 @@ async function infoController(req, res){
logger.info(`/Info Request receive by the Info Controller. URL: ${url}`);
- if (url.includes("?list")) {
+ if (url.includes("&list") || url.includes("?list")) {
logger.info("Estimated Data Type: Playlist")
} else{
diff --git a/server/helpers/getBrowser.js b/server/helpers/getBrowser.js
index f51c8f2..a362a55 100644
--- a/server/helpers/getBrowser.js
+++ b/server/helpers/getBrowser.js
@@ -2,6 +2,7 @@ const fs = require("fs");
const os = require("os");
const path = require("path");
const notify = require("./notify")
+const { logger } = require("../logger");
function getUserBrowser() {
const userProfile = os.homedir();
@@ -18,15 +19,21 @@ function getUserBrowser() {
// { name: "whale", path: path.join(userProfile, "AppData", "Local", "Naver", "Naver Whale", "User Data", "Default") }
];
+ // Chercher un navigateur disponible
for (const browser of browsers) {
if (fs.existsSync(browser.path)) {
+ logger.info(`Browser found: ${browser.name}`);
return browser.name;
- } else{
- notify.notifyFirefoxBrowserMissing();
}
}
- notify.notifyCookiesBrowserError()
- return ;
+
+ // Aucun navigateur trouvé - notifier l'utilisateur
+ logger.warn("No supported browser found on the system");
+ notify.notifyFirefoxBrowserMissing();
+
+ // Fallback: retourner "firefox" pour laisser yt-dlp gérer l'erreur
+ // plutôt que de crasher l'application
+ return "firefox";
}
module.exports = getUserBrowser
\ No newline at end of file
diff --git a/server/helpers/notify.js b/server/helpers/notify.js
index f9a24b7..026cc81 100644
--- a/server/helpers/notify.js
+++ b/server/helpers/notify.js
@@ -1,12 +1,11 @@
const { Notification, shell } = require("electron");
-const path = require("path");
+const { iconPaths } = require("./path");
function notifyDownloadFinished(folder) {
- const iconPath = path.join(process.resourcesPath, "confirm-icon.png");
const notif = new Notification({
title: "Freedom Loader",
body: "Your download is complete, click here to open it.",
- icon: iconPath,
+ icon: iconPaths.confirm,
});
notif.on("click", () => shell.openPath(folder));
@@ -14,11 +13,10 @@ function notifyDownloadFinished(folder) {
}
function notifyCookiesBrowserError(){
- const iconPath = path.join(process.resourcesPath, "error.png");
const notif = new Notification({
title: "Cookies Error",
body: "Unable to retrieve cookies. Please log in to your browser and click here to view the tutorial.",
- icon: iconPath,
+ icon: iconPaths.error,
});
notif.on("click", () => shell.openExternal("https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1"));
@@ -26,11 +24,10 @@ function notifyCookiesBrowserError(){
}
function notifyFirefoxBrowserMissing(){
- const iconPath = path.join(process.ressourcesPath, "error.png");
const notif = new Notification({
title: "Firefox Missing",
body: "Firefox was not found on your system. Click here to follow the installation guide",
- icon: iconPath,
+ icon: iconPaths.error,
});
notif.on("click", () => shell.openExternal("https://youtube.com/shorts/cN9f4s1Mf88?si=519QCVd_-fzJqRf1"))
diff --git a/server/helpers/parseInfo.js b/server/helpers/parseInfo.js
index c397493..6449061 100644
--- a/server/helpers/parseInfo.js
+++ b/server/helpers/parseInfo.js
@@ -2,12 +2,6 @@ function parseVideo(data) {
return {
type: "video",
- // id: data.id,
- // title: data.title,
- // url: data.webpage_url,
- // duration: data.duration,
- // thumbnail: data.thumbnail,
- // uploader: data.uploader
...data
};
}
diff --git a/server/helpers/path.js b/server/helpers/path.js
index 8eac1a5..2a7d823 100644
--- a/server/helpers/path.js
+++ b/server/helpers/path.js
@@ -1,15 +1,25 @@
const path = require("path");
const fs = require("fs");
+const os = require("os");
const { app } = require("electron");
const config = require("../../config");
const { logger } = require("../logger.js");
+// Centralized resource paths
+const resourcesPath = config.localMode
+ ? path.join(__dirname, "../../ressources")
+ : process.resourcesPath;
+
+// Default download folder (centralized)
+const defaultDownloadFolder = path.join(os.homedir(), "Downloads", "Freedom Loader");
+
+// Binary paths
let userYtDlp;
let ffmpegPath;
let denoPath;
-const sourceYtDlp = path.join(process.resourcesPath, "binaries","yt-dlp.exe");
+const sourceYtDlp = path.join(resourcesPath, "binaries","yt-dlp.exe");
if (config.localMode) {
userYtDlp = path.join(__dirname, "../../ressources/yt-dlp.exe");
@@ -17,8 +27,8 @@ if (config.localMode) {
denoPath = path.join(__dirname, "../../ressources/deno.exe");
} else {
userYtDlp = path.join(app.getPath("userData"),"yt-dlp.exe");
- ffmpegPath = path.join(process.resourcesPath, "binaries","ffmpeg.exe");
- denoPath = path.join(process.resourcesPath, "binaries","deno.exe");
+ ffmpegPath = path.join(resourcesPath, "binaries","ffmpeg.exe");
+ denoPath = path.join(resourcesPath, "binaries","deno.exe");
if (!fs.existsSync(userYtDlp)) {
fs.copyFileSync(sourceYtDlp, userYtDlp);
@@ -26,8 +36,23 @@ if (config.localMode) {
}
+// Notification icon paths
+const iconPaths = {
+ confirm: path.join(resourcesPath, "confirm-icon.png"),
+ error: path.join(resourcesPath, "error.png"),
+ app: path.join(resourcesPath, "app-icon.ico")
+};
+
+// Binary paths for verification
+const binaryPaths = {
+ ytDlp: path.join(resourcesPath, "binaries", "yt-dlp.exe"),
+ ffmpeg: path.join(resourcesPath, "binaries", "ffmpeg.exe"),
+ ffprobe: path.join(resourcesPath, "binaries", "ffprobe.exe"),
+ deno: path.join(resourcesPath, "binaries", "deno.exe")
+};
+
if (!userYtDlp){ logger.error("Missing YT-DLP")}
if (!ffmpegPath){ logger.error("Missing FFMPEG")}
if (!denoPath){ logger.error("Missing DENO")}
-module.exports = { userYtDlp, ffmpegPath, denoPath };
+module.exports = { userYtDlp, ffmpegPath, denoPath, iconPaths, binaryPaths, resourcesPath, defaultDownloadFolder };
diff --git a/server/helpers/rateLimit.js b/server/helpers/rateLimit.js
index d65336f..8d85534 100644
--- a/server/helpers/rateLimit.js
+++ b/server/helpers/rateLimit.js
@@ -3,7 +3,7 @@ const RateLimit = require("express-rate-limit");
/* Rate Limite */
const rateLimite = RateLimit({
- windowMs: 15 * 60, // 15 minutes
+ windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 100 requests per windowMs on the root path
message: "Too many requests, please try again later.",
statusCode: 429, // HTTP status code for "Too Many Requests"
diff --git a/server/helpers/validation.js b/server/helpers/validation.js
index b6ede30..b7e3da1 100644
--- a/server/helpers/validation.js
+++ b/server/helpers/validation.js
@@ -13,7 +13,7 @@ function isValidUrl(url) {
function isSafePath(folder) {
if (!folder || folder.length < 3) return false;
- const unsafe = ["System32", "/etc", "\\Windows"];
+ const unsafe = ["System32", "\\Windows"];
const resolved = path.resolve(folder);
return !unsafe.some(u => resolved.includes(u));
}
diff --git a/server/logger.js b/server/logger.js
index 632d553..9e4274c 100644
--- a/server/logger.js
+++ b/server/logger.js
@@ -3,13 +3,17 @@ const DailyRotateFile = require("winston-daily-rotate-file");
const fs = require("fs");
const path = require("path");
const os = require("os");
-const config = require("../config")
+const config = require("../config");
// Dossier de logs Windows
const logDir = path.join(os.homedir(), "AppData", "Local", "FreedomLoader", "logs");
// Création du dossier "logs" si nécessaire
-fs.mkdirSync(logDir, { recursive: true });
+try {
+ fs.mkdirSync(logDir, { recursive: true });
+} catch (error) {
+ console.error(`Failed to create log directory: ${error.message}`);
+}
const logFormat = format.combine(
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
@@ -36,21 +40,13 @@ const logger = createLogger({
],
});
-function getSessionStartLine() {
- return `--- Starting session: ${new Date().toISOString()} ---`;
-}
-
-function getSessionEndLine() {
- return `--- Ending session: ${new Date().toISOString()} ---`;
-}
-
function logSessionStart() {
- logger.info(getSessionStartLine());
+ logger.info(`--- Starting session: ${new Date().toISOString()} ---`);
logger.info(`Application Version: ${config.version}`)
}
function logSessionEnd() {
- logger.info(getSessionEndLine());
+ logger.info(`--- Ending session: ${new Date().toISOString()} ---`);
}
module.exports = {
diff --git a/server/server.js b/server/server.js
index e8f1f75..2f229ed 100644
--- a/server/server.js
+++ b/server/server.js
@@ -1,5 +1,4 @@
const express = require("express");
-const fs = require("fs");
const path = require("path");
const { logger, logSessionEnd } = require("./logger");
const config = require("../config");
@@ -11,19 +10,7 @@ const app = express();
app.use(express.json());
-// Dossier de téléchargement
-const outputFolder = path.join(process.env.USERPROFILE, "Downloads", "Freedom Loader");
-
-// Création du dossier si nécessaire
-try {
- fs.mkdirSync(outputFolder, { recursive: true });
- logger.info(`Freedom Loader folder ready in ${outputFolder}`);
-} catch (err) {
- logger.error("Unable to create folder:", err);
- process.exit(1);
-}
-
-// Mise à jour yt-dlp au
+// Mise à jour yt-dlp au démarrage
execFile(userYtDlp, ["-U"], (err, stdout, stderr) => {
if (err) logger.warn("yt-dlp update error:", err);
else logger.info(`Update yt-dlp : ${stdout}`);
diff --git a/server/services/download.services.js b/server/services/download.services.js
index 789fbd7..d869c4a 100644
--- a/server/services/download.services.js
+++ b/server/services/download.services.js
@@ -1,18 +1,35 @@
const { execFile } = require("child_process");
-const { userYtDlp } = require("../helpers/path");
-const path = require("path");
+const { userYtDlp, defaultDownloadFolder } = require("../helpers/path");
const fs = require("fs");
const { logger } = require("../logger");
const { buildYtDlpArgs } = require("../helpers/buildArgs");
-const notify = require("../helpers/notify")
+const notify = require("../helpers/notify");
+const path = require("path");
+const { isSafePath } = require("../helpers/validation");
function fetchDownload(options, listeners, speedListeners) {
return new Promise((resolve, reject) => {
- const outputFolder = options.outputFolder || path.join(process.env.USERPROFILE, "Downloads", "Freedom Loader");
- fs.mkdirSync(outputFolder, { recursive: true });
+ const outputFolder = options.outputFolder || defaultDownloadFolder;
+
+ // Normalize path and validate it's safe (within Users folder)
+ let safeOutputFolder = path.resolve(outputFolder);
+
+ if (!isSafePath(safeOutputFolder)) {
+ logger.warn(`Path not allowed, using default instead: ${safeOutputFolder}`);
+ safeOutputFolder = path.resolve(defaultDownloadFolder);
+ }
+
+ // Create download folder if it doesn't exist
+ try {
+ fs.mkdirSync(safeOutputFolder, { recursive: true });
+ logger.info(`Output folder ready: ${safeOutputFolder}`);
+ } catch (err) {
+ logger.error(`Failed to create output folder: ${err.message}`);
+ return reject(new Error(`Unable to create download folder: ${err.message}`));
+ }
- const args = buildYtDlpArgs({ ...options, outputFolder });
+ const args = buildYtDlpArgs({ ...options, outputFolder: safeOutputFolder });
logger.info(`[yt-dlp args] ${args.join(" ")}`);
const child = execFile(userYtDlp, args);
@@ -21,7 +38,7 @@ function fetchDownload(options, listeners, speedListeners) {
child.on("close", code => {
listeners.forEach(fn => fn("done"));
- if (code === 0) resolve(outputFolder);
+ if (code === 0) resolve(safeOutputFolder);
else reject(new Error(`YT-DLP failed with code : ${code}`));
});
@@ -31,7 +48,7 @@ function fetchDownload(options, listeners, speedListeners) {
if (!line.trim()) return;
logger.info(`[yt-dlp] ${line}`);
- /* Barre de Chargement*/
+ /* Progress Bar */
if (line.startsWith("[download] Destination:")) listeners.forEach(fn => fn("reset"));
const match = line.match(/\[download\]\s+(\d+\.\d+)%/);
if (match) listeners.forEach(fn => fn(parseFloat(match[1])));