Clarification

This commit is contained in:
MasterAcnolo
2025-06-29 15:48:06 +02:00
parent b4242e4295
commit 9026c63979
4 changed files with 100 additions and 49 deletions

View File

@@ -16,51 +16,61 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
const { createLogger, format, transports } = require("winston"); const { createLogger, format, transports } = require("winston");
const DailyRotateFile = require("winston-daily-rotate-file"); // on importe Winston (logger populaire pour Node.js)
const fs = require("fs");
const path = require("path");
const DailyRotateFile = require("winston-daily-rotate-file");
// plugin pour Winston qui permet la rotation automatique des fichiers de logs
const fs = require("fs"); // pour vérifier/créer le dossier de logs
const path = require("path"); // pour gérer proprement les chemins
// on définit le dossier de logs
const logDir = path.join(__dirname, "../logs"); const logDir = path.join(__dirname, "../logs");
// on le crée si inexistant
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir); if (!fs.existsSync(logDir)) fs.mkdirSync(logDir);
// fonction pour générer une ligne de début de session (date ISO)
function getSessionStartLine() { function getSessionStartLine() {
const now = new Date().toISOString(); const now = new Date().toISOString();
return `--- Démarrage de la session : ${now} ---`; return `--- Démarrage de la session : ${now} ---`;
} }
// fonction pour générer une ligne de fin de session (date ISO)
function getSessionEndLine() { function getSessionEndLine() {
const now = new Date().toISOString(); const now = new Date().toISOString();
return `--- Fin de la session : ${now} ---`; return `--- Fin de la session : ${now} ---`;
} }
// configuration principale de Winston
const logger = createLogger({ const logger = createLogger({
level: "info", level: "info", // niveau de log minimum (info, warn, error, etc.)
format: format.combine( format: format.combine(
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), // timestamp lisible
format.printf(({ timestamp, level, message }) => format.printf(({ timestamp, level, message }) =>
`${timestamp} | ${level.toUpperCase()} | ${message}` `${timestamp} | ${level.toUpperCase()} | ${message}` // format de ligne de log
) )
), ),
transports: [ transports: [
new DailyRotateFile({ new DailyRotateFile({
dirname: logDir, dirname: logDir, // dossier de logs
filename: "combined-%DATE%.log", filename: "combined-%DATE%.log", // nom de fichier journalier
datePattern: "YYYY-MM-DD", datePattern: "YYYY-MM-DD", // pattern de rotation
zippedArchive: false, // tu peux mettre true si tu veux compresser zippedArchive: false, // true = compression des archives
maxFiles: "14d", // conserve les logs des 14 derniers jours maxFiles: "14d", // garde 14 jours dhistorique
format: format.combine( format: format.combine(
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
format.printf(({ timestamp, level, message }) => format.printf(({ timestamp, level, message }) =>
`${timestamp} | ${level.toUpperCase()} | ${message}` `${timestamp} | ${level.toUpperCase()} | ${message}`
) )
), ),
options: { flags: "a" }, options: { flags: "a" }, // mode append
}), }),
new transports.Console({ new transports.Console({
// sortie console colorisée (dev en local)
format: format.combine( format: format.combine(
format.colorize(), format.colorize(),
format.timestamp({ format: "HH:mm:ss" }), format.timestamp({ format: "HH:mm:ss" }), // horodatage plus court
format.printf(({ timestamp, level, message }) => format.printf(({ timestamp, level, message }) =>
`${timestamp} | ${level} | ${message}` `${timestamp} | ${level} | ${message}`
) )
@@ -69,14 +79,17 @@ const logger = createLogger({
], ],
}); });
// fonction d'utilitaire pour marquer le début de la session
function logSessionStart() { function logSessionStart() {
logger.info(getSessionStartLine()); logger.info(getSessionStartLine());
} }
// idem pour la fin de la session
function logSessionEnd() { function logSessionEnd() {
logger.info(getSessionEndLine()); logger.info(getSessionEndLine());
} }
// exporte le logger et les helpers pour les autres modules
module.exports = { module.exports = {
logger, logger,
logSessionStart, logSessionStart,

View File

@@ -16,90 +16,110 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
const express = require("express"); const express = require("express"); // importe le framework Express
const router = express.Router(); const router = express.Router(); // crée un routeur Express pour modulariser les routes
const { execFile } = require("child_process"); const { execFile } = require("child_process"); // permet dexécuter un binaire externe sans passer par un shell
const path = require("path"); const path = require("path"); // module Node.js pour gérer les chemins de fichiers
const fs = require("fs"); const fs = require("fs"); // module Node.js pour manipuler le système de fichiers
const logger = require("../logger").logger; // importe ton logger Winston 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'); const ytDlpPath = path.join(__dirname, '../../yt-dlp.exe');
// route POST sur la racine (ex: POST /download)
router.post("/", (req, res) => { router.post("/", (req, res) => {
try { try {
// récupère les options envoyées depuis le frontend (via formulaire ou requête AJAX)
const options = { const options = {
url: req.body.url, url: req.body.url, // URL à télécharger
audioOnly: req.body.audioOnly === "1", audioOnly: req.body.audioOnly === "1", // conversion audio uniquement (mp3)
quality: req.body.quality || "best", quality: req.body.quality || "best", // qualité vidéo à télécharger
subtitles: req.body.subs === "1", subtitles: req.body.subs === "1", // récupérer les sous-titres anglais
}; };
if (!options.url) { if (!options.url) {
// si lURL est absente, on log et on renvoie 400
logger.warn("Requête POST /download sans URL"); logger.warn("Requête POST /download sans URL");
return res.status(400).send("❌ URL manquante !"); return res.status(400).send("❌ URL manquante !");
} }
// Récupérer le chemin Freedom Loader Output depuis app.locals // on récupère le dossier de sortie depuis app.locals (défini ailleurs dans le serveur)
const outputFolder = req.app.locals.outputFolder; const outputFolder = req.app.locals.outputFolder;
// s'il n'existe pas, on le crée automatiquement
if (!fs.existsSync(outputFolder)) { if (!fs.existsSync(outputFolder)) {
fs.mkdirSync(outputFolder, { recursive: true }); fs.mkdirSync(outputFolder, { recursive: true });
logger.info("Dossier Freedom Loader Output créé à la volée dans Téléchargements."); logger.info("Dossier Freedom Loader Output créé à la volée dans Téléchargements.");
} }
// Construire le template de sortie // template du nom de fichier de sortie (ex: MonTitre.mp4 ou MonTitre.mp3)
const outputTemplate = path.join(outputFolder, "%(title)s.%(ext)s"); const outputTemplate = path.join(outputFolder, "%(title)s.%(ext)s");
// Construire les arguments pour execFile // tableau d'arguments pour yt-dlp
const args = []; const args = [];
if (options.audioOnly) { if (options.audioOnly) {
// si audioOnly, on force extraction audio en mp3
args.push("--extract-audio", "--audio-format", "mp3"); args.push("--extract-audio", "--audio-format", "mp3");
} }
if (options.subtitles) { if (options.subtitles) {
// si lutilisateur veut des sous-titres
args.push("--write-subs", "--sub-lang", "en"); args.push("--write-subs", "--sub-lang", "en");
} }
// qualité souhaitée
args.push("-f", options.quality); args.push("-f", options.quality);
// modèle de sortie
args.push("-o", outputTemplate); args.push("-o", outputTemplate);
// enfin, l'URL à télécharger
args.push(options.url); args.push(options.url);
// log de la commande complète pour debug
logger.info(`Commande yt-dlp : ${ytDlpPath} ${args.join(" ")}`); logger.info(`Commande yt-dlp : ${ytDlpPath} ${args.join(" ")}`);
// exécution du binaire yt-dlp
const child = execFile(ytDlpPath, args); const child = execFile(ytDlpPath, args);
// gestion des logs stdout de yt-dlp
child.stdout.on("data", (data) => { child.stdout.on("data", (data) => {
data.toString().split("\n").forEach(line => { data.toString().split("\n").forEach(line => {
if(line.trim()) logger.info(`[yt-dlp stdout] ${line.trim()}`); if(line.trim()) logger.info(`[yt-dlp stdout] ${line.trim()}`);
}); });
}); });
// gestion des logs stderr de yt-dlp (les erreurs)
child.stderr.on("data", (data) => { child.stderr.on("data", (data) => {
data.toString().split("\n").forEach(line => { data.toString().split("\n").forEach(line => {
if(line.trim()) logger.error(`[yt-dlp stderr] ${line.trim()}`); 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) => { child.on("error", (err) => {
logger.error(`Erreur lancement yt-dlp : ${err.message}`); logger.error(`Erreur lancement yt-dlp : ${err.message}`);
res.status(500).send(`❌ Erreur lors de l'exécution : ${err.message}`); res.status(500).send(`❌ Erreur lors de l'exécution : ${err.message}`);
}); });
// callback quand le processus se termine
child.on("close", (code) => { child.on("close", (code) => {
logger.info(`yt-dlp terminé avec code de sortie : ${code}`); logger.info(`yt-dlp terminé avec code de sortie : ${code}`);
if(code === 0) { if(code === 0) {
// succès
res.send("✅ Téléchargement terminé !"); res.send("✅ Téléchargement terminé !");
} else { } else {
// yt-dlp a échoué
res.status(500).send(`❌ yt-dlp a échoué avec le code : ${code}`); res.status(500).send(`❌ yt-dlp a échoué avec le code : ${code}`);
} }
}); });
} catch (err) { } catch (err) {
// en cas d'erreur JavaScript
logger.error(`Erreur serveur dans /download : ${err.message}`); logger.error(`Erreur serveur dans /download : ${err.message}`);
res.status(500).send(`Erreur serveur : ${err.message}`); res.status(500).send(`Erreur serveur : ${err.message}`);
} }
}); });
module.exports = router; // on exporte ce routeur pour qu'il soit utilisé par l'app principale
module.exports = router;

View File

@@ -16,32 +16,40 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
const express = require("express"); const express = require("express"); // importe Express
const router = express.Router(); const router = express.Router(); // crée un routeur Express indépendant
const { execFile } = require("child_process"); const { execFile } = require("child_process"); // permet dexécuter un programme externe
const path = require("path"); const path = require("path"); // module pour gérer les chemins de fichiers
// chemin vers le binaire yt-dlp embarqué
const ytDlpPath = path.join(__dirname, "../../yt-dlp.exe"); const ytDlpPath = path.join(__dirname, "../../yt-dlp.exe");
// route POST sur la racine de ce routeur
router.post("/", (req, res) => { router.post("/", (req, res) => {
const url = req.body.url; const url = req.body.url; // récupère l'URL depuis la requête POST
if (!url) return res.status(400).send("❌ URL manquante"); if (!url) return res.status(400).send("❌ URL manquante"); // validation simple
// exécute yt-dlp avec --dump-json pour récupérer les métadonnées de la vidéo
execFile(ytDlpPath, ["--dump-json", url], (error, stdout, stderr) => { execFile(ytDlpPath, ["--dump-json", url], (error, stdout, stderr) => {
if (error) { if (error) {
// gestion derreur : par exemple yt-dlp plante ou lURL est invalide
console.error("Erreur lors de l'exécution de yt-dlp :", error); console.error("Erreur lors de l'exécution de yt-dlp :", error);
console.error("stderr :", stderr); console.error("stderr :", stderr);
return res.status(500).send("❌ Impossible de récupérer les infos."); return res.status(500).send("❌ Impossible de récupérer les infos.");
} }
try { try {
// on parse le JSON envoyé par yt-dlp
const info = JSON.parse(stdout); const info = JSON.parse(stdout);
// on renvoie lobjet JSON directement au frontend
res.json(info); res.json(info);
} catch (e) { } catch (e) {
// le JSON est mal formé
console.error("Erreur lors du parsing JSON :", e); console.error("Erreur lors du parsing JSON :", e);
res.status(500).send("❌ JSON illisible."); res.status(500).send("❌ JSON illisible.");
} }
}); });
}); });
// on exporte le routeur pour être utilisé dans app.js ou ailleurs
module.exports = router; module.exports = router;

View File

@@ -16,59 +16,68 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
const express = require("express"); const express = require("express"); // framework web
const { exec } = require("child_process"); const { exec } = require("child_process"); // si besoin dexécuter des commandes (pas utilisé ici directement)
const fs = require("fs"); const fs = require("fs"); // accès au système de fichiers
const path = require("path"); const path = require("path"); // gestion de chemins
const debug = require("debug")("freedom-loader:server"); const debug = require("debug")("freedom-loader:server");
// petit debugger coloré en console
const { logger, logSessionStart, logSessionEnd } = require("./logger"); const { logger, logSessionStart, logSessionEnd } = require("./logger");
// récupère notre logger Winston + helpers de session
const app = express(); const app = express(); // instancie l'application Express
// // Lancement d'une nouvelle session de log // logSessionStart(); // désactivé, mais pourrait logguer le début de session
// logSessionStart();
// dossier de sortie // on définit le dossier de téléchargement dans le dossier Téléchargements de lutilisateur
const downloadsPath = path.join(process.env.USERPROFILE, "Downloads"); const downloadsPath = path.join(process.env.USERPROFILE, "Downloads");
const outputFolder = path.join(downloadsPath, "Freedom Loader Output"); const outputFolder = path.join(downloadsPath, "Freedom Loader Output");
// création du dossier sil nexiste pas
if (!fs.existsSync(outputFolder)) { if (!fs.existsSync(outputFolder)) {
try { try {
fs.mkdirSync(outputFolder, { recursive: true }); fs.mkdirSync(outputFolder, { recursive: true });
logger.info("Dossier Freedom Loader Output cree dans Telechargements."); logger.info("Dossier Freedom Loader Output cree dans Telechargements.");
} catch (err) { } catch (err) {
logger.error("Impossible de creer le dossier :", err); logger.error("Impossible de creer le dossier :", err);
process.exit(1); process.exit(1); // arrêt du programme en cas déchec
} }
} else { } else {
logger.info("Dossier Freedom Loader Output deja existant."); logger.info("Dossier Freedom Loader Output deja existant.");
} }
// on rend ce dossier dispo via app.locals
app.locals.outputFolder = outputFolder; app.locals.outputFolder = outputFolder;
// configuration pour parser les requêtes POST en urlencoded
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
// sert les fichiers statiques (frontend)
const staticPath = path.join(__dirname, "../public"); const staticPath = path.join(__dirname, "../public");
debug("Serveur statique sur", staticPath); debug("Serveur statique sur", staticPath);
app.use(express.static(staticPath)); app.use(express.static(staticPath));
// routes // enregistre les routes API
const infoRoute = require("./routes/info"); const infoRoute = require("./routes/info");
const downloadRoute = require("./routes/download"); const downloadRoute = require("./routes/download");
debug("Routes /download et /info installees"); debug("Routes /download et /info installees");
app.use("/download", downloadRoute); app.use("/download", downloadRoute);
app.use("/info", infoRoute); app.use("/info", infoRoute);
// route GET / de base
app.get("/", (req, res) => { app.get("/", (req, res) => {
debug("Requete GET / servie"); debug("Requete GET / servie");
res.sendFile(path.join(__dirname, "../public/index.html")); res.sendFile(path.join(__dirname, "../public/index.html"));
}); });
// fonction pour démarrer le serveur (Promise pour pouvoir await ailleurs)
function startServer() { function startServer() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
logger.info("Demarrage du serveur Express..."); logger.info("Demarrage du serveur Express...");
const serverInstance = app.listen(8080, () => { const serverInstance = app.listen(8080, () => {
logger.info("Serveur Express pret sur http://localhost:8080"); logger.info("Serveur Express pret sur http://localhost:8080");
resolve(serverInstance); resolve(serverInstance); // succès
}); });
serverInstance.on("error", (err) => { serverInstance.on("error", (err) => {
logger.error("Erreur serveur Express :", err); logger.error("Erreur serveur Express :", err);
@@ -77,17 +86,18 @@ function startServer() {
}); });
} }
// Gestion propre de la fin de session à la fermeture du process // gestion de la fermeture propre du process
// process.on("exit", () => { // process.on("exit", () => {
// logSessionEnd(); // logSessionEnd();
// }); // });
process.on("SIGINT", () => { process.on("SIGINT", () => { // Ctrl+C
logSessionEnd(); logSessionEnd();
process.exit(); process.exit();
}); });
process.on("SIGTERM", () => { process.on("SIGTERM", () => { // kill
logSessionEnd(); logSessionEnd();
process.exit(); process.exit();
}); });
// on exporte la fonction startServer pour que dautres modules puissent la lancer
module.exports = { startServer }; module.exports = { startServer };