mirror of
https://github.com/MasterAcnolo/Freedom-Loader.git
synced 2026-07-29 18:25:47 +02:00
Feat: Add createPlaylistFolders feature to organize downloads into dedicated folders with toggle settings
This commit is contained in:
@@ -17,7 +17,8 @@ const FEATURE_WHITELIST = new Set([
|
|||||||
"verboseLogs",
|
"verboseLogs",
|
||||||
"autoDownloadPlaylist",
|
"autoDownloadPlaylist",
|
||||||
"customCodec",
|
"customCodec",
|
||||||
"theme"
|
"theme",
|
||||||
|
"createPlaylistFolders"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const configFolderPath = featuresPath;
|
const configFolderPath = featuresPath;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"addMetadata": true,
|
"addMetadata": true,
|
||||||
"verboseLogs": false,
|
"verboseLogs": false,
|
||||||
"autoDownloadPlaylist": true,
|
"autoDownloadPlaylist": true,
|
||||||
|
"createPlaylistFolders": true,
|
||||||
"customCodec": "h264",
|
"customCodec": "h264",
|
||||||
"logSystem": true,
|
"logSystem": true,
|
||||||
"outputTitleCheck": true,
|
"outputTitleCheck": true,
|
||||||
|
|||||||
@@ -88,6 +88,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<input type="checkbox" data-key="createPlaylistFolders" id="createPlaylistFolders">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-title">Create Playlist Folders</span>
|
||||||
|
<small>Create a dedicated folder for each playlist with its name.</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- <div class="setting-item">
|
<!-- <div class="setting-item">
|
||||||
<input type="checkbox" data-key="notifySystem" id="notifySystem">
|
<input type="checkbox" data-key="notifySystem" id="notifySystem">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
|
|||||||
@@ -33,6 +33,17 @@ async function init() {
|
|||||||
const urlInput = document.getElementById("UrlInput");
|
const urlInput = document.getElementById("UrlInput");
|
||||||
const infoDiv = document.getElementById("videoInfo");
|
const infoDiv = document.getElementById("videoInfo");
|
||||||
const loaderBox = document.getElementById("loaderBox");
|
const loaderBox = document.getElementById("loaderBox");
|
||||||
|
const form = document.getElementById("downloadForm");
|
||||||
|
|
||||||
|
// Input caché pour stocker le titre de la playlist
|
||||||
|
let playlistTitleInput = document.getElementById("playlistTitle");
|
||||||
|
if (!playlistTitleInput) {
|
||||||
|
playlistTitleInput = document.createElement("input");
|
||||||
|
playlistTitleInput.type = "hidden";
|
||||||
|
playlistTitleInput.name = "playlistTitle";
|
||||||
|
playlistTitleInput.id = "playlistTitle";
|
||||||
|
form.appendChild(playlistTitleInput);
|
||||||
|
}
|
||||||
|
|
||||||
let lastFetchedUrl = "";
|
let lastFetchedUrl = "";
|
||||||
|
|
||||||
@@ -47,6 +58,7 @@ async function init() {
|
|||||||
if (!url || url.length < 2) {
|
if (!url || url.length < 2) {
|
||||||
infoDiv.innerHTML = "";
|
infoDiv.innerHTML = "";
|
||||||
infoDiv.classList.remove("visible", "playlist-mode");
|
infoDiv.classList.remove("visible", "playlist-mode");
|
||||||
|
playlistTitleInput.value = "";
|
||||||
lastFetchedUrl = "";
|
lastFetchedUrl = "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -70,12 +82,14 @@ async function init() {
|
|||||||
`;
|
`;
|
||||||
infoDiv.classList.add("visible");
|
infoDiv.classList.add("visible");
|
||||||
infoDiv.classList.remove("playlist-mode");
|
infoDiv.classList.remove("playlist-mode");
|
||||||
|
playlistTitleInput.value = "";
|
||||||
loaderBox.style.display = "none";
|
loaderBox.style.display = "none";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- PLAYLIST ----------
|
// ---------- PLAYLIST ----------
|
||||||
if (data.type === "playlist") {
|
if (data.type === "playlist") {
|
||||||
|
playlistTitleInput.value = data.title;
|
||||||
infoDiv.classList.add("playlist-mode");
|
infoDiv.classList.add("playlist-mode");
|
||||||
infoDiv.innerHTML = `
|
infoDiv.innerHTML = `
|
||||||
<div class="playlist-header">
|
<div class="playlist-header">
|
||||||
@@ -194,6 +208,7 @@ async function init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
infoDiv.classList.remove("playlist-mode");
|
infoDiv.classList.remove("playlist-mode");
|
||||||
|
playlistTitleInput.value = "";
|
||||||
|
|
||||||
// ---------- VIDEO NORMALE ----------
|
// ---------- VIDEO NORMALE ----------
|
||||||
const durationStr = data.duration
|
const durationStr = data.duration
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ async function downloadController(req, res) {
|
|||||||
audioOnly: req.body.audioOnly === "1",
|
audioOnly: req.body.audioOnly === "1",
|
||||||
quality: req.body.quality || "best",
|
quality: req.body.quality || "best",
|
||||||
outputFolder: req.body.savePath || undefined,
|
outputFolder: req.body.savePath || undefined,
|
||||||
|
playlistTitle: req.body.playlistTitle || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!options.url || !isValidUrl(options.url)) return res.status(400).send("Invalid URL !");
|
if (!options.url || !isValidUrl(options.url)) return res.status(400).send("Invalid URL !");
|
||||||
|
|||||||
@@ -6,13 +6,80 @@ const { buildYtDlpArgs } = require("../helpers/buildArgs.helpers");
|
|||||||
const notify = require("../helpers/notify.helpers");
|
const notify = require("../helpers/notify.helpers");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { isSafePath } = require("../helpers/validation.helpers");
|
const { isSafePath } = require("../helpers/validation.helpers");
|
||||||
|
const { configFeatures } = require("../../config.js");
|
||||||
|
|
||||||
let currentDownloadProcess = null;
|
let currentDownloadProcess = null;
|
||||||
|
|
||||||
|
// Détecte si une URL est une playlist
|
||||||
|
function isPlaylistUrl(url) {
|
||||||
|
return url.includes("?list=") || url.includes("&list=");
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPlaylistFolder(basePath, playlistTitle) {
|
||||||
|
const sanitizedTitle = playlistTitle
|
||||||
|
.replace(/[<>:"|?*]/g, '')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.trim()
|
||||||
|
.substring(0, 200);
|
||||||
|
|
||||||
|
const playlistPath = path.join(basePath, sanitizedTitle);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(playlistPath)) {
|
||||||
|
fs.mkdirSync(playlistPath, { recursive: true });
|
||||||
|
logger.info(`Playlist folder created: ${playlistPath}`);
|
||||||
|
return playlistPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
let counter = 1;
|
||||||
|
let newPath;
|
||||||
|
|
||||||
|
while (counter <= 1000) {
|
||||||
|
newPath = path.join(basePath, `${sanitizedTitle} ${counter}`);
|
||||||
|
if (!fs.existsSync(newPath)) {
|
||||||
|
fs.mkdirSync(newPath, { recursive: true });
|
||||||
|
logger.info(`Playlist folder created with increment: ${newPath}`);
|
||||||
|
return newPath;
|
||||||
|
}
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error(`Could not find available playlist folder after 1000 attempts`);
|
||||||
|
throw new Error("Unable to create playlist folder");
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn(`Failed to create playlist folder with title "${sanitizedTitle}": ${err.message}`);
|
||||||
|
|
||||||
|
// Fallback: create folder "Untitled Playlist X"
|
||||||
|
let counter = 1;
|
||||||
|
let fallbackPath;
|
||||||
|
|
||||||
|
while (counter <= 1000) {
|
||||||
|
fallbackPath = path.join(basePath, `Untitled Playlist ${counter}`);
|
||||||
|
if (!fs.existsSync(fallbackPath)) {
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(fallbackPath, { recursive: true });
|
||||||
|
logger.info(`Fallback playlist folder created: ${fallbackPath}`);
|
||||||
|
return fallbackPath;
|
||||||
|
} catch (mkErr) {
|
||||||
|
counter++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error(`Could not create playlist folder after 1000 attempts`);
|
||||||
|
throw new Error("Unable to create playlist folder");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function fetchDownload(options, listeners, speedListeners, stageListeners) {
|
function fetchDownload(options, listeners, speedListeners, stageListeners) {
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const outputFolder = options.outputFolder || defaultDownloadFolder;
|
logger.info(`CONFIG createPlaylistFolders: ${configFeatures.createPlaylistFolders}`);
|
||||||
|
|
||||||
|
let outputFolder = options.outputFolder || defaultDownloadFolder;
|
||||||
|
|
||||||
// Normalize path and validate it's safe (within Users folder)
|
// Normalize path and validate it's safe (within Users folder)
|
||||||
let safeOutputFolder = path.resolve(outputFolder);
|
let safeOutputFolder = path.resolve(outputFolder);
|
||||||
@@ -31,6 +98,22 @@ function fetchDownload(options, listeners, speedListeners, stageListeners) {
|
|||||||
return reject(new Error(`Unable to create download folder: ${err.message}`));
|
return reject(new Error(`Unable to create download folder: ${err.message}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Détecte si c'est une playlist et crée un dossier approprié
|
||||||
|
const isPlaylist = options.playlistTitle || isPlaylistUrl(options.url);
|
||||||
|
|
||||||
|
if (isPlaylist && configFeatures.createPlaylistFolders) {
|
||||||
|
try {
|
||||||
|
const playlistName = options.playlistTitle || "Untitled Playlist";
|
||||||
|
safeOutputFolder = createPlaylistFolder(safeOutputFolder, playlistName);
|
||||||
|
logger.info(`Playlist detected, using folder: ${safeOutputFolder}`);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`Failed to create playlist folder: ${err.message}`);
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
} else if (isPlaylist && !configFeatures.createPlaylistFolders) {
|
||||||
|
logger.info(`Playlist detected but createPlaylistFolders is disabled, using base folder`);
|
||||||
|
}
|
||||||
|
|
||||||
const args = buildYtDlpArgs({ ...options, outputFolder: safeOutputFolder });
|
const args = buildYtDlpArgs({ ...options, outputFolder: safeOutputFolder });
|
||||||
logger.info(`[yt-dlp args] ${args.join(" ")}`);
|
logger.info(`[yt-dlp args] ${args.join(" ")}`);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user