Files
Freedom-Loader/server/routes/download.js
MasterAcnolo 0ffc1af218 1.0.1 Commit
2025-07-01 15:16:17 +02:00

165 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
This file is part of Freedom Loader.
Copyright (C) 2025 MasterAcnolo
Freedom Loader is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License.
Freedom Loader is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const express = require("express"); // importe le framework Express
const router = express.Router(); // crée un routeur Express pour modulariser les routes
const { execFile } = require("child_process"); // permet dexécuter un binaire externe sans passer par un shell
const path = require("path"); // module Node.js pour gérer les chemins de fichiers
const fs = require("fs"); // module Node.js pour manipuler le système de fichiers
const logger = require("../logger").logger; // récupère l'instance de logger Winston personnalisée
// chemin vers lexécutable yt-dlp packagé avec lappli
const ytDlpPath = path.join(__dirname, '../../yt-dlp.exe');
// route POST sur la racine (ex: POST /download)
router.post("/", (req, res) => {
try {
// récupère les options envoyées depuis le frontend (via formulaire ou requête AJAX)
const options = {
url: req.body.url, // URL à télécharger
audioOnly: req.body.audioOnly === "1", // conversion audio uniquement (mp3)
quality: req.body.quality || "best", // qualité vidéo à télécharger
subtitles: req.body.subs === "1", // récupérer les sous-titres anglais
};
if (!options.url) {
// si lURL est absente, on log et on renvoie 400
logger.warn("Requête POST /download sans URL");
return res.status(400).send("❌ URL manquante !");
}
// on récupère le dossier de sortie depuis app.locals (défini ailleurs dans le serveur)
const outputFolder = req.app.locals.outputFolder;
// s'il n'existe pas, on le crée automatiquement
if (!fs.existsSync(outputFolder)) {
fs.mkdirSync(outputFolder, { recursive: true });
logger.info("Dossier Freedom Loader Output créé à la volée dans Téléchargements.");
}
// template du nom de fichier de sortie (ex: MonTitre.mp4 ou MonTitre.mp3)
const outputTemplate = path.join(outputFolder, "%(title)s.%(ext)s");
// tableau d'arguments pour yt-dlp
const args = [];
if (options.audioOnly) {
// si audioOnly, on force extraction audio en mp3
args.push("--extract-audio", "--audio-format", "mp3");
}
if (options.subtitles) {
// si lutilisateur veut des sous-titres
args.push("--write-subs", "--sub-lang", "en");
}
// // qualité souhaitée
// args.push("-f", options.quality);
// let format;
// if (options.quality === "best") {
// format = "bestvideo+bestaudio/best";
// } else if (options.quality === "worst") {
// format = "worstvideo+worstaudio/worst";
// } else {
// format = "best"; // fallback
// }
// args.push("-f", format);
args.push("--no-continue");
let format;
switch (options.quality) {
case "best":
format = "bestvideo+bestaudio/best";
break;
case "medium":
// qualité moyenne, ex: 720p vidéo + meilleur audio
format = "bestvideo[height<=720]+bestaudio/best[height<=720]";
break;
case "worst":
format = "worstvideo+worstaudio/worst";
break;
case "1080":
format = "bestvideo[height<=1080]+bestaudio/best[height<=1080]";
break;
case "720":
format = "bestvideo[height<=720]+bestaudio/best[height<=720]";
break;
case "480":
format = "bestvideo[height<=480]+bestaudio/best[height<=480]";
break;
default:
format = "best";
}
args.push("-f", format);
// modèle de sortie
args.push("-o", outputTemplate);
// enfin, l'URL à télécharger
args.push(options.url);
// log de la commande complète pour debug
logger.info(`Commande yt-dlp : ${ytDlpPath} ${args.join(" ")}`);
// exécution du binaire yt-dlp
const child = execFile(ytDlpPath, args);
// gestion des logs stdout de yt-dlp
child.stdout.on("data", (data) => {
data.toString().split("\n").forEach(line => {
if(line.trim()) logger.info(`[yt-dlp stdout] ${line.trim()}`);
});
});
// gestion des logs stderr de yt-dlp (les erreurs)
child.stderr.on("data", (data) => {
data.toString().split("\n").forEach(line => {
if(line.trim()) logger.error(`[yt-dlp stderr] ${line.trim()}`);
});
});
// si une erreur système survient (par ex : yt-dlp.exe introuvable)
child.on("error", (err) => {
logger.error(`Erreur lancement yt-dlp : ${err.message}`);
res.status(500).send(`❌ Erreur lors de l'exécution : ${err.message}`);
});
// callback quand le processus se termine
child.on("close", (code) => {
logger.info(`yt-dlp terminé avec code de sortie : ${code}`);
if(code === 0) {
// succès
res.send("✅ Téléchargement terminé !");
} else {
// yt-dlp a échoué
res.status(500).send(`❌ yt-dlp a échoué avec le code : ${code}`);
}
});
} catch (err) {
// en cas d'erreur JavaScript
logger.error(`Erreur serveur dans /download : ${err.message}`);
res.status(500).send(`Erreur serveur : ${err.message}`);
}
});
// on exporte ce routeur pour qu'il soit utilisé par l'app principale
module.exports = router;