mirror of
https://github.com/MasterAcnolo/Freedom-Loader.git
synced 2026-07-29 18:25:47 +02:00
Merge pull request #1 from MasterAcnolo/Développement-Branch
[PR] 1.0.1
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,4 +6,3 @@
|
|||||||
|
|
||||||
|
|
||||||
yt-dlp.exe
|
yt-dlp.exe
|
||||||
forge.config.js
|
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
# Freedom Loader
|
# Freedom Loader
|
||||||
|
|
||||||

|
<!-- Release -->
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<!-- Build -->
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
> 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
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
# Freedom Loader
|
# Freedom Loader
|
||||||
|
|
||||||

|
<!-- Release -->
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<!-- Build -->
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
> 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
|
||||||
|
|
||||||
|
|||||||
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() {
|
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
2308
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||||
|
|||||||
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) -->
|
<!-- <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>
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
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 { 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 d’historique
|
||||||
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,
|
||||||
|
|||||||
@@ -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 d’exé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 l’exécutable yt-dlp packagé avec l’appli
|
||||||
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 l’URL 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 l’utilisateur 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;
|
||||||
@@ -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 d’exé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 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("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 l’objet 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;
|
||||||
|
|||||||
@@ -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 d’exé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 l’utilisateur
|
||||||
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 s’il n’existe 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 d’autres modules puissent la lancer
|
||||||
module.exports = { startServer };
|
module.exports = { startServer };
|
||||||
|
|||||||
Reference in New Issue
Block a user