22 Commits
1.0.0 ... 1.0.1

Author SHA1 Message Date
MasterAcnolo
251049c7fe Undo last Commit 2025-07-01 15:37:13 +02:00
MasterAcnolo
5d347967b1 ReadMe Fix 2025-07-01 15:36:33 +02:00
MasterAcnolo
d5c506c4e0 ReadME Fix %20 2025-07-01 15:35:47 +02:00
MasterAcnolo
6718f4133e ReadME Fix %20 2025-07-01 15:35:24 +02:00
MasterAcnolo
9ff6678730 ReadME Fix 2025-07-01 15:34:36 +02:00
MasterAcnolo
3957450fdb ReadME Fix 2025-07-01 15:34:06 +02:00
MasterAcnolo
25e50a6e7a ReadME Fix 2025-07-01 15:33:31 +02:00
MasterAcnolo
22f935f678 ReadME Fix 2025-07-01 15:32:04 +02:00
MasterAcnolo
58b843630b ReadME Fix 2025-07-01 15:30:54 +02:00
MasterAcnolo
dec3f3272a ReadME Fix 2025-07-01 15:30:23 +02:00
MasterAcnolo
f3252c2548 ReadME Fix 2025-07-01 15:29:18 +02:00
MasterAcnolo
ac28afb17a ReadME Fix 2025-07-01 15:27:44 +02:00
MasterAcnolo
b1a42f44fa ReadME Fix 2025-07-01 15:26:21 +02:00
MasterAcnolo
78eea2fa0f ReadME Fix 2025-07-01 15:25:21 +02:00
MasterAcnolo
4290e6db52 Merge pull request #1 from MasterAcnolo/Développement-Branch
[PR] 1.0.1
2025-07-01 15:21:11 +02:00
MasterAcnolo
0ffc1af218 1.0.1 Commit 2025-07-01 15:16:17 +02:00
MasterAcnolo
10cefdba19 CSS Variable Begin 2025-07-01 13:51:42 +02:00
MasterAcnolo
c888946588 Logs Name Ajust + 1.0.1 Prepa 2025-06-30 13:49:02 +02:00
MasterAcnolo
9026c63979 Clarification 2025-06-29 15:48:06 +02:00
MasterAcnolo
cfc2c5bbaa Update README-EN.md 2025-06-29 14:13:24 +02:00
MasterAcnolo
a41da5c4c2 Update README.md 2025-06-29 14:12:40 +02:00
MasterAcnolo
b4242e4295 Correction Git Ignore 2025-06-29 14:10:58 +02:00
18 changed files with 748 additions and 1874 deletions

1
.gitignore vendored
View File

@@ -6,4 +6,3 @@
yt-dlp.exe yt-dlp.exe
forge.config.js

View File

@@ -1,6 +1,6 @@
# Freedom Loader # Freedom Loader
![Static Badge](https://img.shields.io/badge/Version-1.0.0-blue?style=flat&logo=Test) ![Static Badge](https://img.shields.io/badge/Latest%20Release_-1.0.1-blue?style=flat) ![Static Badge](https://img.shields.io/badge/Build-1.0.2%20Beta-yellow?style=flat)
> This project aims to enable free, open-source, and unrestricted downloading of Video/Audio content. Eventually, it will allow users to choose > This project aims to enable free, open-source, and unrestricted downloading of Video/Audio content. Eventually, it will allow users to choose
@@ -70,7 +70,7 @@ This project is open-source and welcomes your contributions.
Here is how to get involved: Here is how to get involved:
- **🪲 Bug Reporting** - **🪲 Bug Reporting**
Please use the [GitHub issues system](https://github.com/MasterAcnolo/votre-repo/issues) to report any bugs you encounter. Please use the [GitHub issues system](https://github.com/MasterAcnolo/Freedom-Loader/issues) to report any bugs you encounter.
Try to provide clear reproduction steps and logs if possible. Logs are located in the installation folder of the program. Example path: Try to provide clear reproduction steps and logs if possible. Logs are located in the installation folder of the program. Example path:
`C:\Users\[YOUR NAME]\Desktop\Freedom Loader\resources\app\logs` `C:\Users\[YOUR NAME]\Desktop\Freedom Loader\resources\app\logs`
The log file will be named something like `combined-20xx-xx-xx.log` The log file will be named something like `combined-20xx-xx-xx.log`
@@ -79,7 +79,7 @@ Here is how to get involved:
Pull Requests are welcome! Please fork the repo and submit your PR after ensuring you meet the contribution guidelines (tests, style, etc.). Pull Requests are welcome! Please fork the repo and submit your PR after ensuring you meet the contribution guidelines (tests, style, etc.).
<br> <br>
- **💡 Feature Suggestions** - **💡 Feature Suggestions**
You can propose new ideas via a [feature request issue](https://github.com/votre-utilisateur/votre-repo/issues). You can propose new ideas via a [feature request issue](https://github.com/MasterAcnolo/Freedom-Loader/issues).
Any clear and detailed proposal will be reviewed. Any clear and detailed proposal will be reviewed.
<br> <br>
- **💰 Support the Project** - **💰 Support the Project**

View File

@@ -1,6 +1,6 @@
# Freedom Loader # Freedom Loader
![Static Badge](https://img.shields.io/badge/Version-1.0.0-blue?style=flat&logo=Test) ![Static Badge](https://img.shields.io/badge/Latest%20Release_-1.0.1-blue?style=flat) ![Static Badge](https://img.shields.io/badge/Build-1.0.2%20Beta-yellow?style=flat)
> Ce projet a pour but de permettre le téléchargement de contenu Vidéo/Audio de manière gratuite, open source et libre. Il permettra à terme de choisir > Ce projet a pour but de permettre le téléchargement de contenu Vidéo/Audio de manière gratuite, open source et libre. Il permettra à terme de choisir
@@ -70,7 +70,7 @@ Ce projet est open source et accueille volontiers vos contributions.
Voici comment participer : Voici comment participer :
- **🪲 Signalement de bugs** - **🪲 Signalement de bugs**
Merci dutiliser le [système dissues GitHub](https://github.com/MasterAcnolo/votre-repo/issues) pour remonter les bugs rencontrés. Merci dutiliser le [système dissues GitHub](https://github.com/MasterAcnolo/Freedom-Loader/issues) pour remonter les bugs rencontrés.
Essayez de fournir une reproduction claire et les logs si possible. Les logs se trouvent dans le dossier dinstallation du programme. Exemple de chemin : Essayez de fournir une reproduction claire et les logs si possible. Les logs se trouvent dans le dossier dinstallation du programme. Exemple de chemin :
`C:\Users\[VOTRE NOM]\Desktop\Freedom Loader\resources\app\logs` `C:\Users\[VOTRE NOM]\Desktop\Freedom Loader\resources\app\logs`
Le fichier aura un nom du type `combined-20xx-xx-xx.log`. Le fichier aura un nom du type `combined-20xx-xx-xx.log`.
@@ -79,7 +79,7 @@ Voici comment participer :
Les Pull Requests sont les bienvenues ! Merci de forker le repo et de soumettre votre PR après avoir vérifié le respect des guidelines (tests, style, etc.). Les Pull Requests sont les bienvenues ! Merci de forker le repo et de soumettre votre PR après avoir vérifié le respect des guidelines (tests, style, etc.).
<br> <br>
- **💡 Suggestions de fonctionnalités** - **💡 Suggestions de fonctionnalités**
Vous pouvez proposer de nouvelles idées via une [issue de type feature request](https://github.com/votre-utilisateur/votre-repo/issues). Vous pouvez proposer de nouvelles idées via une [issue de type feature request](https://github.com/MasterAcnolo/Freedom-Loader/issues).
Toute proposition claire et détaillée sera étudiée. Toute proposition claire et détaillée sera étudiée.
<br> <br>
- **💰 Soutenir le projet** - **💰 Soutenir le projet**

44
forge.config.js Normal file
View File

@@ -0,0 +1,44 @@
const { FusesPlugin } = require('@electron-forge/plugin-fuses');
const { FuseV1Options, FuseVersion } = require('@electron/fuses');
module.exports = {
packagerConfig: {
asar: true,
},
rebuildConfig: {},
makers: [
{
name: '@electron-forge/maker-squirrel',
config: {},
},
{
name: '@electron-forge/maker-zip',
platforms: ['darwin'],
},
{
name: '@electron-forge/maker-deb',
config: {},
},
{
name: '@electron-forge/maker-rpm',
config: {},
},
],
plugins: [
{
name: '@electron-forge/plugin-auto-unpack-natives',
config: {},
},
// Fuses are used to enable/disable various Electron functionality
// at package time, before code signing the application
new FusesPlugin({
version: FuseVersion.V1,
[FuseV1Options.RunAsNode]: false,
[FuseV1Options.EnableCookieEncryption]: true,
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
[FuseV1Options.EnableNodeCliInspectArguments]: false,
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
[FuseV1Options.OnlyLoadAppFromAsar]: true,
}),
],
};

View File

@@ -24,17 +24,22 @@ let mainWindow;
async function createWindow() { async function createWindow() {
logger.info("Creation de la fenetre..."); logger.info("Creation de la fenetre...");
if (mainWindow) { if (mainWindow) {
logger.warn("La fenetre existe deja, pas de nouvelle creation"); logger.warn("La fenetre existe deja, pas de nouvelle creation");
return; return;
} }
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
width: 1000, title: "Freedom Loader",
height: 700, width: 750,
height: 800,
minWidth: 750,
minHeight: 800,
webPreferences: { webPreferences: {
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,
}, },
// titleBarStyle: 'hidden',
}); });
try { try {

2308
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "freedom-loader", "name": "freedom-loader",
"productName": "Freedom Loader", "productName": "Freedom Loader",
"version": "1.0.0", "version": "1.0.1",
"author": "MasterAcnolo", "author": "MasterAcnolo",
"description": "Freedom Loader, parce que nous le valons bien", "description": "Freedom Loader, parce que nous le valons bien",
"main": "main.js", "main": "main.js",

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -0,0 +1,3 @@
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z" transform="scale(64)" fill="#1B1F23"/>
</svg>

After

Width:  |  Height:  |  Size: 968 B

View File

@@ -41,8 +41,13 @@
<!-- <input type="checkbox" name="audioOnly" value="1"> Audio seulement (MP3) --> <!-- <input type="checkbox" name="audioOnly" value="1"> Audio seulement (MP3) -->
<select name="quality"> <select name="quality">
<option value="best">Meilleure qualité</option> <option value="best">Meilleure qualité</option>
<option value="medium">Qualité moyenne</option>
<option value="worst">Qualité minimale</option> <option value="worst">Qualité minimale</option>
<option value="1080">1080p</option>
<option value="720">720p</option>
<option value="480">480p</option>
</select> </select>
<button type="submit">Télécharger</button> <button type="submit">Télécharger</button>
</form> </form>

View File

@@ -1,5 +1,3 @@
/* From Uiverse.io by bociKond */
/* Hide the default checkbox */
.checkbox-mp3 input { .checkbox-mp3 input {
position: absolute; position: absolute;
opacity: 0; opacity: 0;
@@ -16,9 +14,8 @@
user-select: none; user-select: none;
} }
/* Create a custom checkbox */
.checkmark { .checkmark {
--clr: #0B6E4F; --clr: #3B82F6 ;
position: relative; position: relative;
top: 0; top: 0;
left: 0; left: 0;
@@ -29,26 +26,22 @@
transition: 300ms; transition: 300ms;
} }
/* When the checkbox is checked, add a blue background */
.checkbox-mp3 input:checked ~ .checkmark { .checkbox-mp3 input:checked ~ .checkmark {
background-color: var(--clr); background-color: var(--clr);
border-radius: .5rem; border-radius: .5rem;
animation: pulse 500ms ease-in-out; animation: pulse 500ms ease-in-out;
} }
/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after { .checkmark:after {
content: ""; content: "";
position: absolute; position: absolute;
display: none; display: none;
} }
/* Show the checkmark when checked */
.checkbox-mp3 input:checked ~ .checkmark:after { .checkbox-mp3 input:checked ~ .checkmark:after {
display: block; display: block;
} }
/* Style the checkmark/indicator */
.container .checkmark:after { .container .checkmark:after {
left: 0.45em; left: 0.45em;
top: 0.25em; top: 0.25em;
@@ -61,7 +54,7 @@
@keyframes pulse { @keyframes pulse {
0% { 0% {
box-shadow: 0 0 0 #0B6E4F90; box-shadow: 0 0 0 var(--checkbox-pulse-color);
rotate: 20deg; rotate: 20deg;
} }
@@ -70,11 +63,11 @@
} }
75% { 75% {
box-shadow: 0 0 0 10px #0B6E4F60; box-shadow: 0 0 0 10px var(--checkbox-pulse-color);
} }
100% { 100% {
box-shadow: 0 0 0 13px #0B6E4F30; box-shadow: 0 0 0 13px var(--checkbox-pulse-color);
rotate: 0; rotate: 0;
} }
} }

View File

@@ -4,7 +4,7 @@
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
padding: 1.5em 2em; padding: 1.5em 2em;
background-color: #fff; background-color: var(--infos-box-color);
border-radius: 12px; border-radius: 12px;
box-shadow: 0 8px 16px rgba(0,0,0,0.1); box-shadow: 0 8px 16px rgba(0,0,0,0.1);
color: #222; color: #222;

View File

@@ -3,7 +3,8 @@
@import url("layout/ctr-infos.css"); @import url("layout/ctr-infos.css");
@import url("layout/header.css"); @import url("layout/header.css");
@import url("layout/videoinfo.css"); @import url("layout/videoinfo.css");
@import url("button/checkbox.css"); @import url("components/checkbox.css");
@import url("variables.css");
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
@@ -11,22 +12,22 @@ html,body{
margin: 0; margin: 0;
padding: 0; padding: 0;
scroll-behavior: smooth; scroll-behavior: smooth;
/* outline: 2px solid red; */ /* outline: 2px solid red; */ /*<-- Utile pour le début ça */
font-family: "Poppins", sans-serif; font-family: "Poppins", sans-serif;
font-weight: 500; font-weight: 500;
font-style: normal; font-style: normal;
} }
body { body {
margin: 0; margin: 0;
/* background-color: #f5f5f5; */ background-color: var(--background-color);
background-image: linear-gradient(to left top, #838383, #a1a1a1, #bfbfbf, #dfdfdf, #ffffff); /* background-image: linear-gradient(to left top, #838383, #a1a1a1, #bfbfbf, #dfdfdf, #ffffff);
background-size: cover; background-size: cover;
background-position: center; background-position: center;
background-attachment: fixed; background-attachment: fixed; */
width: 100%; width: 100%;
height: 100%; height: 100%;
color: #333; color: var(--default-text-color);
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@@ -34,9 +35,15 @@ body {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
header p{
color: var(--subtitle-color);
}
.checkbox-inline{
color: var(--mp3-text-color);
}
/* #videoInfo { /* #videoInfo {
margin-top: 100px; margin-top: 100px;

View File

@@ -0,0 +1,11 @@
:root{
--background-color : #0F172A;
--infos-box-color: #f1f1f1 ;
--title-color: ;
--subtitle-color : #a3aab4;
--mp3-text-color: #0F172A;
--default-text-color: #F8FAFC;
--checkbox-pulse-color:#9bbcf1;
}

View File

@@ -18,44 +18,50 @@
const { createLogger, format, transports } = require("winston"); const { createLogger, format, transports } = require("winston");
const DailyRotateFile = require("winston-daily-rotate-file"); const DailyRotateFile = require("winston-daily-rotate-file");
const fs = require("fs");
const path = require("path");
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
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
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: "LOGS-%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({
format: format.combine( format: format.combine(
@@ -69,14 +75,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());
} }
// 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,150 @@
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");
} }
args.push("-f", options.quality); // // 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); 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}`);
} }
}); });
// on exporte ce routeur pour qu'il soit utilisé par l'app principale
module.exports = router; 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 };