mirror of
https://github.com/MasterAcnolo/Freedom-Loader.git
synced 2026-07-29 18:25:47 +02:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
251049c7fe | ||
|
|
5d347967b1 | ||
|
|
d5c506c4e0 | ||
|
|
6718f4133e | ||
|
|
9ff6678730 | ||
|
|
3957450fdb | ||
|
|
25e50a6e7a | ||
|
|
22f935f678 | ||
|
|
58b843630b | ||
|
|
dec3f3272a | ||
|
|
f3252c2548 | ||
|
|
ac28afb17a | ||
|
|
b1a42f44fa | ||
|
|
78eea2fa0f | ||
|
|
4290e6db52 | ||
|
|
0ffc1af218 | ||
|
|
10cefdba19 | ||
|
|
c888946588 | ||
|
|
9026c63979 | ||
|
|
cfc2c5bbaa | ||
|
|
a41da5c4c2 | ||
|
|
b4242e4295 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,4 +6,3 @@
|
||||
|
||||
|
||||
yt-dlp.exe
|
||||
forge.config.js
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Freedom Loader
|
||||
|
||||

|
||||
 
|
||||
|
||||
> 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:
|
||||
|
||||
- **🪲 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:
|
||||
`C:\Users\[YOUR NAME]\Desktop\Freedom Loader\resources\app\logs`
|
||||
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.).
|
||||
<br>
|
||||
- **💡 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.
|
||||
<br>
|
||||
- **💰 Support the Project**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Freedom Loader
|
||||
|
||||

|
||||
 
|
||||
|
||||
> 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 :
|
||||
|
||||
- **🪲 Signalement de bugs**
|
||||
Merci d’utiliser le [système d’issues GitHub](https://github.com/MasterAcnolo/votre-repo/issues) pour remonter les bugs rencontrés.
|
||||
Merci d’utiliser le [système d’issues 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 d’installation du programme. Exemple de chemin :
|
||||
`C:\Users\[VOTRE NOM]\Desktop\Freedom Loader\resources\app\logs`
|
||||
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.).
|
||||
<br>
|
||||
- **💡 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.
|
||||
<br>
|
||||
- **💰 Soutenir le projet**
|
||||
|
||||
44
forge.config.js
Normal file
44
forge.config.js
Normal 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,
|
||||
}),
|
||||
],
|
||||
};
|
||||
9
main.js
9
main.js
@@ -24,17 +24,22 @@ let mainWindow;
|
||||
|
||||
async function createWindow() {
|
||||
logger.info("Creation de la fenetre...");
|
||||
|
||||
if (mainWindow) {
|
||||
logger.warn("La fenetre existe deja, pas de nouvelle creation");
|
||||
return;
|
||||
}
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1000,
|
||||
height: 700,
|
||||
title: "Freedom Loader",
|
||||
width: 750,
|
||||
height: 800,
|
||||
minWidth: 750,
|
||||
minHeight: 800,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
},
|
||||
// titleBarStyle: 'hidden',
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
2308
package-lock.json
generated
2308
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "freedom-loader",
|
||||
"productName": "Freedom Loader",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"author": "MasterAcnolo",
|
||||
"description": "Freedom Loader, parce que nous le valons bien",
|
||||
"main": "main.js",
|
||||
|
||||
BIN
public/assets/icons/GitHub Icon.ico
Normal file
BIN
public/assets/icons/GitHub Icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
3
public/assets/icons/GitHub Icon.svg
Normal file
3
public/assets/icons/GitHub Icon.svg
Normal 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 |
@@ -41,8 +41,13 @@
|
||||
<!-- <input type="checkbox" name="audioOnly" value="1"> Audio seulement (MP3) -->
|
||||
<select name="quality">
|
||||
<option value="best">Meilleure qualité</option>
|
||||
<option value="medium">Qualité moyenne</option>
|
||||
<option value="worst">Qualité minimale</option>
|
||||
<option value="1080">1080p</option>
|
||||
<option value="720">720p</option>
|
||||
<option value="480">480p</option>
|
||||
</select>
|
||||
|
||||
<button type="submit">Télécharger</button>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* From Uiverse.io by bociKond */
|
||||
/* Hide the default checkbox */
|
||||
.checkbox-mp3 input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
@@ -16,9 +14,8 @@
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Create a custom checkbox */
|
||||
.checkmark {
|
||||
--clr: #0B6E4F;
|
||||
--clr: #3B82F6 ;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@@ -29,26 +26,22 @@
|
||||
transition: 300ms;
|
||||
}
|
||||
|
||||
/* When the checkbox is checked, add a blue background */
|
||||
.checkbox-mp3 input:checked ~ .checkmark {
|
||||
background-color: var(--clr);
|
||||
border-radius: .5rem;
|
||||
animation: pulse 500ms ease-in-out;
|
||||
}
|
||||
|
||||
/* Create the checkmark/indicator (hidden when not checked) */
|
||||
.checkmark:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Show the checkmark when checked */
|
||||
.checkbox-mp3 input:checked ~ .checkmark:after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Style the checkmark/indicator */
|
||||
.container .checkmark:after {
|
||||
left: 0.45em;
|
||||
top: 0.25em;
|
||||
@@ -61,7 +54,7 @@
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 #0B6E4F90;
|
||||
box-shadow: 0 0 0 var(--checkbox-pulse-color);
|
||||
rotate: 20deg;
|
||||
}
|
||||
|
||||
@@ -70,11 +63,11 @@
|
||||
}
|
||||
|
||||
75% {
|
||||
box-shadow: 0 0 0 10px #0B6E4F60;
|
||||
box-shadow: 0 0 0 10px var(--checkbox-pulse-color);
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow: 0 0 0 13px #0B6E4F30;
|
||||
box-shadow: 0 0 0 13px var(--checkbox-pulse-color);
|
||||
rotate: 0;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 1.5em 2em;
|
||||
background-color: #fff;
|
||||
background-color: var(--infos-box-color);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
|
||||
color: #222;
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
@import url("layout/ctr-infos.css");
|
||||
@import url("layout/header.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');
|
||||
|
||||
@@ -11,7 +12,7 @@ html,body{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
scroll-behavior: smooth;
|
||||
/* outline: 2px solid red; */
|
||||
/* outline: 2px solid red; */ /*<-- Utile pour le début ça */
|
||||
font-family: "Poppins", sans-serif;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
@@ -19,14 +20,14 @@ html,body{
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
/* background-color: #f5f5f5; */
|
||||
background-image: linear-gradient(to left top, #838383, #a1a1a1, #bfbfbf, #dfdfdf, #ffffff);
|
||||
background-color: var(--background-color);
|
||||
/* background-image: linear-gradient(to left top, #838383, #a1a1a1, #bfbfbf, #dfdfdf, #ffffff);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-attachment: fixed;
|
||||
background-attachment: fixed; */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #333;
|
||||
color: var(--default-text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -34,9 +35,15 @@ body {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
|
||||
}
|
||||
|
||||
header p{
|
||||
color: var(--subtitle-color);
|
||||
}
|
||||
|
||||
.checkbox-inline{
|
||||
color: var(--mp3-text-color);
|
||||
}
|
||||
|
||||
/* #videoInfo {
|
||||
margin-top: 100px;
|
||||
|
||||
11
public/styles/variables.css
Normal file
11
public/styles/variables.css
Normal 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;
|
||||
|
||||
}
|
||||
@@ -18,44 +18,50 @@
|
||||
|
||||
const { createLogger, format, transports } = require("winston");
|
||||
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");
|
||||
// on le crée si inexistant
|
||||
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir);
|
||||
|
||||
// fonction pour générer une ligne de début de session
|
||||
function getSessionStartLine() {
|
||||
const now = new Date().toISOString();
|
||||
return `--- Démarrage de la session : ${now} ---`;
|
||||
}
|
||||
|
||||
// fonction pour générer une ligne de fin de session
|
||||
function getSessionEndLine() {
|
||||
const now = new Date().toISOString();
|
||||
return `--- Fin de la session : ${now} ---`;
|
||||
}
|
||||
|
||||
// configuration principale de Winston
|
||||
const logger = createLogger({
|
||||
level: "info",
|
||||
level: "info", // niveau de log minimum (info, warn, error, etc.)
|
||||
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 }) =>
|
||||
`${timestamp} | ${level.toUpperCase()} | ${message}`
|
||||
`${timestamp} | ${level.toUpperCase()} | ${message}` // format de ligne de log
|
||||
)
|
||||
),
|
||||
transports: [
|
||||
new DailyRotateFile({
|
||||
dirname: logDir,
|
||||
filename: "combined-%DATE%.log",
|
||||
datePattern: "YYYY-MM-DD",
|
||||
zippedArchive: false, // tu peux mettre true si tu veux compresser
|
||||
maxFiles: "14d", // conserve les logs des 14 derniers jours
|
||||
dirname: logDir, // dossier de logs
|
||||
filename: "LOGS-%DATE%.log", // nom de fichier journalier
|
||||
datePattern: "YYYY-MM-DD", // pattern de rotation
|
||||
zippedArchive: false, // true = compression des archives
|
||||
maxFiles: "14d", // garde 14 jours d’historique
|
||||
format: format.combine(
|
||||
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
|
||||
format.printf(({ timestamp, level, message }) =>
|
||||
`${timestamp} | ${level.toUpperCase()} | ${message}`
|
||||
)
|
||||
),
|
||||
options: { flags: "a" },
|
||||
options: { flags: "a" }, // mode append
|
||||
}),
|
||||
new transports.Console({
|
||||
format: format.combine(
|
||||
@@ -69,14 +75,17 @@ const logger = createLogger({
|
||||
],
|
||||
});
|
||||
|
||||
// fonction d'utilitaire pour marquer le début de la session
|
||||
function logSessionStart() {
|
||||
logger.info(getSessionStartLine());
|
||||
}
|
||||
|
||||
// pour la fin de la session
|
||||
function logSessionEnd() {
|
||||
logger.info(getSessionEndLine());
|
||||
}
|
||||
|
||||
// exporte le logger et les helpers pour les autres modules
|
||||
module.exports = {
|
||||
logger,
|
||||
logSessionStart,
|
||||
|
||||
@@ -16,90 +16,150 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
const { execFile } = require("child_process");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
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 d’exé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; // importe ton logger Winston
|
||||
const logger = require("../logger").logger; // récupère l'instance de logger Winston personnalisée
|
||||
|
||||
// chemin vers l’exécutable yt-dlp packagé avec l’appli
|
||||
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,
|
||||
audioOnly: req.body.audioOnly === "1",
|
||||
quality: req.body.quality || "best",
|
||||
subtitles: req.body.subs === "1",
|
||||
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 l’URL est absente, on log et on renvoie 400
|
||||
logger.warn("Requête POST /download sans URL");
|
||||
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;
|
||||
|
||||
// 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.");
|
||||
}
|
||||
|
||||
// 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");
|
||||
|
||||
// Construire les arguments pour execFile
|
||||
// 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 l’utilisateur veut des sous-titres
|
||||
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);
|
||||
// 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;
|
||||
@@ -16,32 +16,40 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
const { execFile } = require("child_process");
|
||||
const path = require("path");
|
||||
const express = require("express"); // importe Express
|
||||
const router = express.Router(); // crée un routeur Express indépendant
|
||||
const { execFile } = require("child_process"); // permet d’exécuter un programme externe
|
||||
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");
|
||||
|
||||
// route POST sur la racine de ce routeur
|
||||
router.post("/", (req, res) => {
|
||||
const url = req.body.url;
|
||||
if (!url) return res.status(400).send("❌ URL manquante");
|
||||
const url = req.body.url; // récupère l'URL depuis la requête POST
|
||||
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) => {
|
||||
if (error) {
|
||||
// gestion d’erreur : par exemple yt-dlp plante ou l’URL est invalide
|
||||
console.error("Erreur lors de l'exécution de yt-dlp :", error);
|
||||
console.error("stderr :", stderr);
|
||||
return res.status(500).send("❌ Impossible de récupérer les infos.");
|
||||
}
|
||||
|
||||
try {
|
||||
// on parse le JSON envoyé par yt-dlp
|
||||
const info = JSON.parse(stdout);
|
||||
// on renvoie l’objet JSON directement au frontend
|
||||
res.json(info);
|
||||
} catch (e) {
|
||||
// le JSON est mal formé
|
||||
console.error("Erreur lors du parsing JSON :", e);
|
||||
res.status(500).send("❌ JSON illisible.");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// on exporte le routeur pour être utilisé dans app.js ou ailleurs
|
||||
module.exports = router;
|
||||
|
||||
@@ -16,59 +16,68 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const express = require("express");
|
||||
const { exec } = require("child_process");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const express = require("express"); // framework web
|
||||
const { exec } = require("child_process"); // si besoin d’exécuter des commandes (pas utilisé ici directement)
|
||||
const fs = require("fs"); // accès au système de fichiers
|
||||
const path = require("path"); // gestion de chemins
|
||||
const debug = require("debug")("freedom-loader:server");
|
||||
// petit debugger coloré en console
|
||||
|
||||
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();
|
||||
// logSessionStart(); // désactivé, mais pourrait logguer le début de session
|
||||
|
||||
// dossier de sortie
|
||||
// on définit le dossier de téléchargement dans le dossier Téléchargements de l’utilisateur
|
||||
const downloadsPath = path.join(process.env.USERPROFILE, "Downloads");
|
||||
const outputFolder = path.join(downloadsPath, "Freedom Loader Output");
|
||||
|
||||
// création du dossier s’il n’existe pas
|
||||
if (!fs.existsSync(outputFolder)) {
|
||||
try {
|
||||
fs.mkdirSync(outputFolder, { recursive: true });
|
||||
logger.info("Dossier Freedom Loader Output cree dans Telechargements.");
|
||||
} catch (err) {
|
||||
logger.error("Impossible de creer le dossier :", err);
|
||||
process.exit(1);
|
||||
process.exit(1); // arrêt du programme en cas d’échec
|
||||
}
|
||||
} else {
|
||||
logger.info("Dossier Freedom Loader Output deja existant.");
|
||||
}
|
||||
|
||||
// on rend ce dossier dispo via app.locals
|
||||
app.locals.outputFolder = outputFolder;
|
||||
|
||||
// configuration pour parser les requêtes POST en urlencoded
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// sert les fichiers statiques (frontend)
|
||||
const staticPath = path.join(__dirname, "../public");
|
||||
debug("Serveur statique sur", staticPath);
|
||||
app.use(express.static(staticPath));
|
||||
|
||||
// routes
|
||||
// enregistre les routes API
|
||||
const infoRoute = require("./routes/info");
|
||||
const downloadRoute = require("./routes/download");
|
||||
debug("Routes /download et /info installees");
|
||||
app.use("/download", downloadRoute);
|
||||
app.use("/info", infoRoute);
|
||||
|
||||
// route GET / de base
|
||||
app.get("/", (req, res) => {
|
||||
debug("Requete GET / servie");
|
||||
res.sendFile(path.join(__dirname, "../public/index.html"));
|
||||
});
|
||||
|
||||
// fonction pour démarrer le serveur (Promise pour pouvoir await ailleurs)
|
||||
function startServer() {
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.info("Demarrage du serveur Express...");
|
||||
const serverInstance = app.listen(8080, () => {
|
||||
logger.info("Serveur Express pret sur http://localhost:8080");
|
||||
resolve(serverInstance);
|
||||
resolve(serverInstance); // succès
|
||||
});
|
||||
serverInstance.on("error", (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", () => {
|
||||
// logSessionEnd();
|
||||
// });
|
||||
process.on("SIGINT", () => {
|
||||
process.on("SIGINT", () => { // Ctrl+C
|
||||
logSessionEnd();
|
||||
process.exit();
|
||||
});
|
||||
process.on("SIGTERM", () => {
|
||||
process.on("SIGTERM", () => { // kill
|
||||
logSessionEnd();
|
||||
process.exit();
|
||||
});
|
||||
|
||||
// on exporte la fonction startServer pour que d’autres modules puissent la lancer
|
||||
module.exports = { startServer };
|
||||
|
||||
Reference in New Issue
Block a user