mirror of
https://github.com/MasterAcnolo/Freedom-Loader.git
synced 2026-07-29 18:25:47 +02:00
Feat: Implement Config File. Read by Front and Back. It aims to give possibility to disable some features quickly. Currenty: DiscordRPC, AutoCheckInfo, AutoUpdate, Custom TopBar, AddMetadata, Add Thumbail were put and implement. Other need to be implement. That's the begin of Settings Panel
This commit is contained in:
29
config.js
29
config.js
@@ -1,5 +1,20 @@
|
|||||||
const packageJson = require("./package.json");
|
const packageJson = require("./package.json");
|
||||||
const { app } = require("electron");
|
const { app } = require("electron");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const featuresPath = path.join(__dirname, "./config/config.json");
|
||||||
|
|
||||||
|
let features = {};
|
||||||
|
|
||||||
|
function loadFeatures() {
|
||||||
|
const raw = fs.readFileSync(featuresPath, "utf-8");
|
||||||
|
features = JSON.parse(raw);
|
||||||
|
console.log(features)
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
||||||
|
const configFeatures = loadFeatures();
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
version: packageJson.version,
|
version: packageJson.version,
|
||||||
@@ -7,17 +22,5 @@ module.exports = {
|
|||||||
debugMode: true,
|
debugMode: true,
|
||||||
localMode: !app.isPackaged,
|
localMode: !app.isPackaged,
|
||||||
DiscordRPCID: "1410934537051181146",
|
DiscordRPCID: "1410934537051181146",
|
||||||
|
configFeatures
|
||||||
// Variables Used to toggle main features
|
|
||||||
autoUpdate: true,
|
|
||||||
discordRPC: true,
|
|
||||||
customTopBar: true, // (Will be active on next launch)
|
|
||||||
autoDownloadPlaylist: true,
|
|
||||||
logSystem: true, // Disable = Dangerous
|
|
||||||
autoCheckInfo: true, // To Improve speed ? (NO)
|
|
||||||
outputTitleCheck: true, // For Non latin characters (Russian)
|
|
||||||
addThumbail: true, // The Pictures in the files (audio files)
|
|
||||||
addMetadata: true, // Looks Explicit
|
|
||||||
downloadSystem: true, // Why would you disable this ? I don't know but why not
|
|
||||||
notifySystem: true // Notification when download
|
|
||||||
}
|
}
|
||||||
13
config/config.json
Normal file
13
config/config.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"autoUpdate": true,
|
||||||
|
"discordRPC": true,
|
||||||
|
"customTopBar": true,
|
||||||
|
"autoDownloadPlaylist": true,
|
||||||
|
"logSystem": true,
|
||||||
|
"autoCheckInfo": true,
|
||||||
|
"outputTitleCheck": true,
|
||||||
|
"addThumbnail": true,
|
||||||
|
"addMetadata": true,
|
||||||
|
"downloadSystem": true,
|
||||||
|
"notifySystem": true
|
||||||
|
}
|
||||||
15
main.js
15
main.js
@@ -3,8 +3,11 @@ const { app, BrowserWindow, ipcMain, dialog, Menu, shell } = require("electron")
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const os = require("os");
|
const os = require("os");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
|
||||||
const { logger, logSessionStart, logSessionEnd, logDir } = require("./server/logger");
|
const { logger, logSessionStart, logSessionEnd, logDir } = require("./server/logger");
|
||||||
const { AutoUpdater } = require("./server/update.js");
|
const { AutoUpdater } = require("./server/update.js");
|
||||||
|
const { configFeatures } = require("./config.js");
|
||||||
|
const { startRPC } = require("./server/discordRPC");
|
||||||
|
|
||||||
let mainWindow;
|
let mainWindow;
|
||||||
const logsFolderPath = logDir;
|
const logsFolderPath = logDir;
|
||||||
@@ -77,7 +80,7 @@ async function createMainWindow() {
|
|||||||
height: 800,
|
height: 800,
|
||||||
minWidth: 750,
|
minWidth: 750,
|
||||||
minHeight: 800,
|
minHeight: 800,
|
||||||
frame:false,
|
frame: !configFeatures.customTopBar,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
@@ -163,11 +166,15 @@ app.whenReady().then(async () => {
|
|||||||
await expressServer.startServer();
|
await expressServer.startServer();
|
||||||
logger.info("Express Server Started");
|
logger.info("Express Server Started");
|
||||||
|
|
||||||
const { startRPC } = require("./server/discordRPC");
|
ipcMain.handle("features", () => {
|
||||||
startRPC();
|
return configFeatures;
|
||||||
|
});
|
||||||
|
|
||||||
|
configFeatures.discordRPC ? startRPC() : "";
|
||||||
|
|
||||||
await createMainWindow();
|
await createMainWindow();
|
||||||
AutoUpdater(mainWindow);
|
configFeatures.AutoUpdater ? AutoUpdater(mainWindow) : ""; // Auto Update
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error("Window or Server error :", err);
|
logger.error("Window or Server error :", err);
|
||||||
app.quit();
|
app.quit();
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ const { contextBridge, ipcRenderer } = require("electron");
|
|||||||
contextBridge.exposeInMainWorld("electronAPI", {
|
contextBridge.exposeInMainWorld("electronAPI", {
|
||||||
getDefaultDownloadPath: () => ipcRenderer.invoke("get-default-download-path"),
|
getDefaultDownloadPath: () => ipcRenderer.invoke("get-default-download-path"),
|
||||||
selectDownloadFolder: () => ipcRenderer.invoke("select-download-folder"),
|
selectDownloadFolder: () => ipcRenderer.invoke("select-download-folder"),
|
||||||
setProgress: (percent) => ipcRenderer.send("set-progress", percent)
|
setProgress: (percent) => ipcRenderer.send("set-progress", percent),
|
||||||
|
getFeatures: () => ipcRenderer.invoke("features")
|
||||||
});
|
});
|
||||||
|
|
||||||
// Contrôles de fenêtre et outils custom pour la topbar
|
// Contrôles de fenêtre et outils custom pour la topbar
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="topbar">
|
<div class="topbar" id="topbar">
|
||||||
<div class="custom-controls">
|
<div class="custom-controls">
|
||||||
<button id="devtools-btn" title="Dev Tools">Tools</button>
|
<button id="devtools-btn" title="Dev Tools">Tools</button>
|
||||||
<button id="logs-btn" title="Logs">Logs</button>
|
<button id="logs-btn" title="Logs">Logs</button>
|
||||||
@@ -31,12 +31,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="theme-switcher">
|
<div class="theme-switcher" id="theme-switcher">
|
||||||
<label for="themeSelect">Thème :</label>
|
<label for="themeSelect">Thème :</label>
|
||||||
<select id="themeSelect" aria-label="Choisir le thème"></select>
|
<select id="themeSelect" aria-label="Choisir le thème"></select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container" id="container">
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<h1 id="title"> Freedom Loader </h1>
|
<h1 id="title"> Freedom Loader </h1>
|
||||||
|
|||||||
@@ -25,156 +25,164 @@ async function fetchVideoInfo(url) {
|
|||||||
return { error: "Network or JSON Issue" };
|
return { error: "Network or JSON Issue" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function init() {
|
||||||
|
const configFeatures = await window.electronAPI.getFeatures();
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
if (!configFeatures.autoCheckInfo) return;
|
||||||
const urlInput = document.getElementById("UrlInput");
|
|
||||||
const infoDiv = document.getElementById("videoInfo");
|
|
||||||
const loaderBox = document.getElementById("loaderBox");
|
|
||||||
|
|
||||||
let lastFetchedUrl = "";
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const urlInput = document.getElementById("UrlInput");
|
||||||
|
const infoDiv = document.getElementById("videoInfo");
|
||||||
|
const loaderBox = document.getElementById("loaderBox");
|
||||||
|
|
||||||
urlInput.addEventListener("input", async () => {
|
let lastFetchedUrl = "";
|
||||||
const url = urlInput.value.trim();
|
|
||||||
|
|
||||||
// Si champ vide -> reset total
|
urlInput.addEventListener("input", async () => {
|
||||||
if (!url || url.length < 2) {
|
const url = urlInput.value.trim();
|
||||||
infoDiv.innerHTML = "";
|
|
||||||
infoDiv.classList.remove("visible", "playlist-mode");
|
|
||||||
lastFetchedUrl = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url === lastFetchedUrl) return;
|
// Si champ vide -> reset total
|
||||||
lastFetchedUrl = url;
|
if (!url || url.length < 2) {
|
||||||
|
infoDiv.innerHTML = "";
|
||||||
|
infoDiv.classList.remove("visible", "playlist-mode");
|
||||||
|
lastFetchedUrl = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
loaderBox.style.display = "flex";
|
if (url === lastFetchedUrl) return;
|
||||||
const data = await fetchVideoInfo(url);
|
lastFetchedUrl = url;
|
||||||
loaderBox.style.display = "none";
|
|
||||||
|
|
||||||
// Gestion des erreurs
|
loaderBox.style.display = "flex";
|
||||||
if (data.error) {
|
const data = await fetchVideoInfo(url);
|
||||||
infoDiv.innerHTML = `
|
|
||||||
<div style="
|
|
||||||
padding:7px;
|
|
||||||
background:var(----infos-box-color);
|
|
||||||
">
|
|
||||||
<strong>${data.error}</strong>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
infoDiv.classList.add("visible");
|
|
||||||
infoDiv.classList.remove("playlist-mode");
|
|
||||||
loaderBox.style.display = "none";
|
loaderBox.style.display = "none";
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- PLAYLIST ----------
|
// Gestion des erreurs
|
||||||
if (data.type === "playlist") {
|
if (data.error) {
|
||||||
infoDiv.classList.add("playlist-mode");
|
infoDiv.innerHTML = `
|
||||||
infoDiv.innerHTML = `
|
<div style="
|
||||||
<h3 style="color:var(--video-info-heading-color);"><strong>Playlist Detected: ${data.title}</strong></h3>
|
padding:7px;
|
||||||
<h3 style="color:var(--video-info-heading-color);"><strong>Video Count: ${data.count}</strong></h3>
|
background:var(----infos-box-color);
|
||||||
<p><strong>Channel :</strong> ${data.channel || "Unknown"}</p>
|
">
|
||||||
<div id="playlistVideos"></div>
|
<strong>${data.error}</strong>
|
||||||
`;
|
|
||||||
|
|
||||||
const listDiv = document.getElementById("playlistVideos");
|
|
||||||
|
|
||||||
data.videos.forEach(v => {
|
|
||||||
const durationStr = v.duration
|
|
||||||
? `${Math.floor(v.duration / 60)}m ${(v.duration % 60).toString().padStart(2,"0")}s` : "Inconnue";
|
|
||||||
|
|
||||||
const videoUrl = v.id ? `https://www.youtube.com/watch?v=${v.id}` : v.url;
|
|
||||||
|
|
||||||
listDiv.innerHTML += `
|
|
||||||
<div style="margin-bottom:12px;">
|
|
||||||
<img src="${v.thumbnail}" width="160" alt="Thumbnail">
|
|
||||||
<p><strong>${v.title}</strong></p>
|
|
||||||
<p>Duration : ${durationStr}</p>
|
|
||||||
<p><a href="${videoUrl}" target="_blank">URL</a>
|
|
||||||
<button class="copy-btn" data-url="${videoUrl}">📋</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
});
|
infoDiv.classList.add("visible");
|
||||||
|
infoDiv.classList.remove("playlist-mode");
|
||||||
|
loaderBox.style.display = "none";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Gestion du bouton copier
|
// ---------- PLAYLIST ----------
|
||||||
listDiv.addEventListener("click", (event) => {
|
if (data.type === "playlist") {
|
||||||
if (event.target.classList.contains("copy-btn")) {
|
infoDiv.classList.add("playlist-mode");
|
||||||
const btn = event.target;
|
infoDiv.innerHTML = `
|
||||||
if (btn.disabled) return;
|
<h3 style="color:var(--video-info-heading-color);"><strong>Playlist Detected: ${data.title}</strong></h3>
|
||||||
|
<h3 style="color:var(--video-info-heading-color);"><strong>Video Count: ${data.count}</strong></h3>
|
||||||
|
<p><strong>Channel :</strong> ${data.channel || "Unknown"}</p>
|
||||||
|
<div id="playlistVideos"></div>
|
||||||
|
`;
|
||||||
|
|
||||||
btn.disabled = true;
|
const listDiv = document.getElementById("playlistVideos");
|
||||||
const url = btn.dataset.url;
|
|
||||||
navigator.clipboard.writeText(url)
|
|
||||||
.then(() => {
|
|
||||||
const original = btn.textContent;
|
|
||||||
|
|
||||||
btn.style.opacity = 0;
|
data.videos.forEach(v => {
|
||||||
btn.style.transform = "scale(0.7)";
|
const durationStr = v.duration
|
||||||
|
? `${Math.floor(v.duration / 60)}m ${(v.duration % 60).toString().padStart(2,"0")}s` : "Inconnue";
|
||||||
|
|
||||||
setTimeout(() => {
|
const videoUrl = v.id ? `https://www.youtube.com/watch?v=${v.id}` : v.url;
|
||||||
btn.textContent = "✅";
|
|
||||||
btn.style.opacity = 1;
|
listDiv.innerHTML += `
|
||||||
btn.style.transform = "scale(1)";
|
<div style="margin-bottom:12px;">
|
||||||
|
<img src="${v.thumbnail}" width="160" alt="Thumbnail">
|
||||||
|
<p><strong>${v.title}</strong></p>
|
||||||
|
<p>Duration : ${durationStr}</p>
|
||||||
|
<p><a href="${videoUrl}" target="_blank">URL</a>
|
||||||
|
<button class="copy-btn" data-url="${videoUrl}">📋</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gestion du bouton copier
|
||||||
|
listDiv.addEventListener("click", (event) => {
|
||||||
|
if (event.target.classList.contains("copy-btn")) {
|
||||||
|
const btn = event.target;
|
||||||
|
if (btn.disabled) return;
|
||||||
|
|
||||||
|
btn.disabled = true;
|
||||||
|
const url = btn.dataset.url;
|
||||||
|
navigator.clipboard.writeText(url)
|
||||||
|
.then(() => {
|
||||||
|
const original = btn.textContent;
|
||||||
|
|
||||||
|
btn.style.opacity = 0;
|
||||||
|
btn.style.transform = "scale(0.7)";
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
btn.style.opacity = 0;
|
btn.textContent = "✅";
|
||||||
btn.style.transform = "scale(0.7)";
|
btn.style.opacity = 1;
|
||||||
|
btn.style.transform = "scale(1)";
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
btn.textContent = original;
|
btn.style.opacity = 0;
|
||||||
btn.style.opacity = 1;
|
btn.style.transform = "scale(0.7)";
|
||||||
btn.style.transform = "scale(1)";
|
|
||||||
btn.disabled = false;
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
}, 1000);
|
setTimeout(() => {
|
||||||
|
btn.textContent = original;
|
||||||
|
btn.style.opacity = 1;
|
||||||
|
btn.style.transform = "scale(1)";
|
||||||
|
btn.disabled = false;
|
||||||
|
}, 300);
|
||||||
|
|
||||||
}, 300);
|
}, 1000);
|
||||||
})
|
|
||||||
.catch(() => {
|
}, 300);
|
||||||
const original = btn.textContent;
|
})
|
||||||
btn.textContent = "❌";
|
.catch(() => {
|
||||||
setTimeout(() => {
|
const original = btn.textContent;
|
||||||
btn.textContent = original;
|
btn.textContent = "❌";
|
||||||
btn.disabled = false;
|
setTimeout(() => {
|
||||||
}, 1500);
|
btn.textContent = original;
|
||||||
});
|
btn.disabled = false;
|
||||||
}
|
}, 1500);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
infoDiv.classList.add("visible");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
infoDiv.classList.remove("playlist-mode");
|
||||||
|
|
||||||
|
// ---------- VIDEO NORMALE ----------
|
||||||
|
const durationStr = data.duration
|
||||||
|
? `${Math.floor(data.duration/60)}m ${(data.duration%60).toString().padStart(2,"0")}s`
|
||||||
|
: "Unknown";
|
||||||
|
|
||||||
|
const sizeStr = formatSize(data.filesize_approx);
|
||||||
|
const readableDate = formatDate(data.upload_date);
|
||||||
|
const categories = data.categories?.join(", ") || "Non spécifiées";
|
||||||
|
|
||||||
|
infoDiv.innerHTML = `
|
||||||
|
<h3>${data.title}</h3>
|
||||||
|
<img src="${data.thumbnail}" width="320" alt="Thumbnail">
|
||||||
|
<ul>
|
||||||
|
<li><strong>Duration :</strong> ${durationStr}</li>
|
||||||
|
<li><strong>Uploader :</strong> ${data.uploader || "Inconnu"}</li>
|
||||||
|
<li><strong>Upload Date :</strong> ${readableDate}</li>
|
||||||
|
<li><strong>Views :</strong> ${data.view_count?.toLocaleString() || "?"}</li>
|
||||||
|
<li><strong>Likes :</strong> ${data.like_count?.toLocaleString() || "?"}</li>
|
||||||
|
<li><strong>URL :</strong> <a href="${data.webpage_url}" target="_blank">${data.webpage_url}</a></li>
|
||||||
|
<li><strong>Channel :</strong> <a href="${data.channel_url}" target="_blank">${data.channel_url}</a></li>
|
||||||
|
<li><strong>Estimed Size :</strong> ${sizeStr}</li>
|
||||||
|
<li><strong>Category :</strong> ${categories}</li>
|
||||||
|
</ul>
|
||||||
|
`;
|
||||||
|
|
||||||
infoDiv.classList.add("visible");
|
infoDiv.classList.add("visible");
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
infoDiv.classList.remove("playlist-mode");
|
})
|
||||||
|
|
||||||
// ---------- VIDEO NORMALE ----------
|
};
|
||||||
const durationStr = data.duration
|
|
||||||
? `${Math.floor(data.duration/60)}m ${(data.duration%60).toString().padStart(2,"0")}s`
|
|
||||||
: "Unknown";
|
|
||||||
|
|
||||||
const sizeStr = formatSize(data.filesize_approx);
|
init();
|
||||||
const readableDate = formatDate(data.upload_date);
|
|
||||||
const categories = data.categories?.join(", ") || "Non spécifiées";
|
|
||||||
|
|
||||||
infoDiv.innerHTML = `
|
|
||||||
<h3>${data.title}</h3>
|
|
||||||
<img src="${data.thumbnail}" width="320" alt="Thumbnail">
|
|
||||||
<ul>
|
|
||||||
<li><strong>Duration :</strong> ${durationStr}</li>
|
|
||||||
<li><strong>Uploader :</strong> ${data.uploader || "Inconnu"}</li>
|
|
||||||
<li><strong>Upload Date :</strong> ${readableDate}</li>
|
|
||||||
<li><strong>Views :</strong> ${data.view_count?.toLocaleString() || "?"}</li>
|
|
||||||
<li><strong>Likes :</strong> ${data.like_count?.toLocaleString() || "?"}</li>
|
|
||||||
<li><strong>URL :</strong> <a href="${data.webpage_url}" target="_blank">${data.webpage_url}</a></li>
|
|
||||||
<li><strong>Channel :</strong> <a href="${data.channel_url}" target="_blank">${data.channel_url}</a></li>
|
|
||||||
<li><strong>Estimed Size :</strong> ${sizeStr}</li>
|
|
||||||
<li><strong>Category :</strong> ${categories}</li>
|
|
||||||
</ul>
|
|
||||||
`;
|
|
||||||
|
|
||||||
infoDiv.classList.add("visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
})
|
|
||||||
@@ -21,4 +21,12 @@ function setupTopbarListeners() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setupTopbarListeners();
|
setupTopbarListeners(); // IF it put it the if check. It don't work. Why ?
|
||||||
|
|
||||||
|
const features = await window.electronAPI.getFeatures();
|
||||||
|
|
||||||
|
if(!features.customTopBar){
|
||||||
|
document.getElementById("topbar").style.display = "none";
|
||||||
|
document.getElementById("container").style.marginTop = "0";
|
||||||
|
document.getElementById("theme-switcher").style.top = "30px";
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ const path = require("path");
|
|||||||
|
|
||||||
const getUserBrowser = require("./getBrowser");
|
const getUserBrowser = require("./getBrowser");
|
||||||
const { ffmpegPath, denoPath} = require("./path");
|
const { ffmpegPath, denoPath} = require("./path");
|
||||||
|
const { configFeatures } = require("../../config.js");
|
||||||
|
|
||||||
|
|
||||||
function buildYtDlpArgs({ url, audioOnly, quality, outputFolder }) {
|
function buildYtDlpArgs({ url, audioOnly, quality, outputFolder }) {
|
||||||
@@ -10,8 +11,8 @@ function buildYtDlpArgs({ url, audioOnly, quality, outputFolder }) {
|
|||||||
"--cookies-from-browser", `${getUserBrowser()}`,
|
"--cookies-from-browser", `${getUserBrowser()}`,
|
||||||
"--no-continue",
|
"--no-continue",
|
||||||
"--no-overwrites",
|
"--no-overwrites",
|
||||||
"--embed-thumbnail",
|
configFeatures.addThumbail ? "--embed-thumbnail" : null,
|
||||||
"--add-metadata",
|
configFeatures.addMetadata ? "--add-metadata" : null,
|
||||||
"--concurrent-fragments", "8",
|
"--concurrent-fragments", "8",
|
||||||
"--retries", "10",
|
"--retries", "10",
|
||||||
"--fragment-retries", "10",
|
"--fragment-retries", "10",
|
||||||
@@ -37,7 +38,7 @@ function buildYtDlpArgs({ url, audioOnly, quality, outputFolder }) {
|
|||||||
args.push("-o", path.join(outputFolder, "%(title)s.%(ext)s"));
|
args.push("-o", path.join(outputFolder, "%(title)s.%(ext)s"));
|
||||||
args.push(url);
|
args.push(url);
|
||||||
|
|
||||||
return args;
|
return args.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { buildYtDlpArgs };
|
module.exports = { buildYtDlpArgs };
|
||||||
|
|||||||
Reference in New Issue
Block a user