11 Commits

Author SHA1 Message Date
MasterAcnolo
90d8a9f65d fix: correct use of logger for path.helpers.js 2026-07-22 20:41:54 +02:00
MasterAcnolo
d5965c2ce0 fix: bring back theme features for Linux 2026-07-22 18:44:30 +02:00
MasterAcnolo
15f59a6093 fix: bring back theme features for Linux 2026-07-22 18:16:33 +02:00
MasterAcnolo
8b31436e25 feat: publish package to fedora copr, add script to automaticaly build and upload the file.
Enhance readme and fix auto updater issue
2026-06-29 10:44:17 +02:00
MasterAcnolo
7d5203001b add: BINARIES.md readme file. About how to install the binaries for dev mode 2026-06-28 21:49:53 +02:00
MasterAcnolo
60709c4fad fix: don't show dialog box if no previous version is available. 2026-06-28 21:48:24 +02:00
MasterAcnolo
2fb6509edc add: dark.zip and white.zip theme to the repo 2026-06-28 21:34:26 +02:00
MasterAcnolo
eebce83976 fix: small code quality and mismatch. 2026-06-28 21:34:00 +02:00
MasterAcnolo
a56a070bc8 feat: support Linux. Refactor resources architecture. Create build script
Currently, themes are disabled and firefox notification too.
2026-06-28 21:32:49 +02:00
MasterAcnolo
e1ef7032bb add: default theme applied in app. If no theme is there 2026-06-18 21:55:50 +02:00
MasterAcnolo
e7333b7b6b fix: missing enableHardwareAcceleration settings in readme 2026-06-10 21:43:38 +02:00
30 changed files with 801 additions and 213 deletions

26
.gitignore vendored
View File

@@ -1,15 +1,23 @@
# Dependencies
/node_modules /node_modules
/downloads
# Build Output Folder
/dist /dist
/out /out
/ressources/ffmpeg.exe # Config JSON File. Create it and fill it if you want to have dev only settings
/ressources/ffprobe.exe
/ressources/deno.exe
/theme/**
config/config.dev.json config/config.dev.json
logs/*.json # IDE
logs/*.log /.idea
/.vscode
# Linux executable
ffmpeg
ffprobe
deno
# Windows Executable
ffmpeg.exe
ffprobe.exe
deno.exe

96
BINARIES.md Normal file
View File

@@ -0,0 +1,96 @@
# External binaries
This project depends on three external tools that are **not bundled in the repository**.
You must download them manually and place each binary in the correct folder before building or running the app locally.
---
## Required tools
| Tool | Role |
|---------|---------------------------------------------|
| yt-dlp | Video and audio download engine |
| ffmpeg | Media encoding, transcoding, muxing |
| ffprobe | Media file analysis and metadata extraction |
| deno | JavaScript / TypeScript runtime |
---
## Folder structure
Place the binaries exactly as shown below.
Do not rename them — the application resolves them by these exact names.
```
resources/
└── binaries/
├── linux/
│ ├── yt-dlp
│ ├── ffmpeg
│ ├── ffprobe
│ └── deno
└── win-32/
├── yt-dlp.exe
├── ffmpeg.exe
├── ffprobe.exe
└── deno.exe
```
---
## Download links
### yt-dlp
| Platform | URL |
|----------|----------------------------------------------------------------------|
| Windows | https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe |
| Linux | https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp |
Rename the downloaded file to `yt-dlp` (or `yt-dlp.exe` on Windows).
---
### ffmpeg and ffprobe
**Windows and Linux** — both binaries are included in the same archive.
| Platform | URL |
|-------------|-----------------------------------------------------------------------------------------------------------|
| Windows x64 | https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip |
| Linux x64 | https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linux64-gpl.tar.xz |
| Linux arm64 | https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linuxarm64-gpl.tar.xz |
Extract the archive and copy `ffmpeg` and `ffprobe` from the `bin/` subfolder.
---
### deno
| Platform | URL |
|-------------|----------------------------------------------------------------------------------------------|
| Windows x64 | https://github.com/denoland/deno/releases/latest/download/deno-x86_64-pc-windows-msvc.zip |
| Linux x64 | https://github.com/denoland/deno/releases/latest/download/deno-x86_64-unknown-linux-gnu.zip |
| Linux arm64 | https://github.com/denoland/deno/releases/latest/download/deno-aarch64-unknown-linux-gnu.zip |
Each archive contains a single binary. Rename it to `deno` (or `deno.exe` on Windows).
---
## Permissions (Linux )
After placing the binaries, make them executable:
```bash
chmod +x resources/binaries/linux/yt-dlp
chmod +x resources/binaries/linux/ffmpeg
chmod +x resources/binaries/linux/ffprobe
chmod +x resources/binaries/linux/deno
```
---
## Notes
- These binaries are excluded from version control via `.gitignore`.
- Always use the latest stable release of each tool.

View File

@@ -105,7 +105,7 @@ Freedom-Loader/
│ ├── controller/ # Business logic │ ├── controller/ # Business logic
│ └── helpers/ # Utility functions │ └── helpers/ # Utility functions
├── public/ # Frontend (HTML, CSS, JS) ├── public/ # Frontend (HTML, CSS, JS)
└── ressources/ # Binaries (yt-dlp, ffmpeg, etc.) └── resources/ # Binaries (yt-dlp, ffmpeg, etc.)
``` ```
--- ---

145
README.md
View File

@@ -5,9 +5,9 @@
alt="Banner" alt="Banner"
style="width: 50%;"/></a> style="width: 50%;"/></a>
### **A clean, open-source multimedia downloader for Windows** ### **A clean, open-source multimedia downloader for Windows and Linux**
[![Release](https://img.shields.io/badge/Release-1.5.1-blue?style=for-the-badge)](https://github.com/MasterAcnolo/Freedom-Loader/releases) [![Release](https://img.shields.io/badge/Release-1.6.0-blue?style=for-the-badge)](https://github.com/MasterAcnolo/Freedom-Loader/releases)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-red.svg?style=for-the-badge)](https://www.gnu.org/licenses/gpl-3.0) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-red.svg?style=for-the-badge)](https://www.gnu.org/licenses/gpl-3.0)
[![Website](https://img.shields.io/badge/Website-Visit-404040?style=for-the-badge)](https://masteracnolo.github.io/Freedom-Loader-Site/) [![Website](https://img.shields.io/badge/Website-Visit-404040?style=for-the-badge)](https://masteracnolo.github.io/Freedom-Loader-Site/)
[![Workshop](https://img.shields.io/badge/Worshop-Visit-0E05A1?style=for-the-badge)](https://masteracnolo.github.io/Freedom-Loader-Workshop/) [![Workshop](https://img.shields.io/badge/Worshop-Visit-0E05A1?style=for-the-badge)](https://masteracnolo.github.io/Freedom-Loader-Workshop/)
@@ -86,35 +86,84 @@ The primary goal is to make media downloading accessible to users who want offli
## Installation ## Installation
[![Fedora Copr](https://img.shields.io/badge/Fedora-Copr-294172?style=for-the-badge&logo=fedora&logoColor=white)](https://copr.fedorainfracloud.org/coprs/masteracnolo/freedom-loader/)
[![Debian/Ubuntu](https://img.shields.io/badge/.deb-Debian%2FUbuntu-A81D33?style=for-the-badge&logo=debian&logoColor=white)](https://github.com/MasterAcnolo/Freedom-Loader/releases)
[//]: # ([![Snap Store]&#40;https://img.shields.io/badge/Snap-Store-82BEA0?style=for-the-badge&logo=snapcraft&logoColor=white&#41;]&#40;https://snapcraft.io/freedom-loader&#41;)
[![AppImage](https://img.shields.io/badge/AppImage-Universal-1E88E5?style=for-the-badge&logo=linux&logoColor=white)](https://github.com/MasterAcnolo/Freedom-Loader/releases)
### Requirements ### Requirements
- **Operating System**: Windows 10 or later - **Windows**: Windows 10 or later
- **Browser**: Mozilla Firefox (required for cookie extraction) - **Linux**: any modern x86_64 distribution
- [Download Firefox](https://www.firefox.com) - **Browser**: Mozilla Firefox ([download](https://www.firefox.com)) required for cookie extraction, on both platforms
### Standard Installation ### Windows
1. Download the latest release from the [Releases page](https://github.com/MasterAcnolo/Freedom-Loader/releases) 1. Download the latest `.exe` from the [Releases page](https://github.com/MasterAcnolo/Freedom-Loader/releases)
2. Run the downloaded `.exe` installer 2. Run the installer (click "More info" → "Run anyway" if Windows Defender warns you)
3. If Windows Defender shows a warning, click "More info" then "Run anyway" 3. Follow the installation wizard
4. Follow the installation wizard
### Linux
#### Fedora (via Copr — recommended)
Installing via Copr lets `dnf` handle future updates automatically:
```bash
sudo dnf copr enable masteracnolo/freedom-loader
sudo dnf install freedom-loader
```
#### Debian / Ubuntu (.deb)
```bash
# Download the latest .deb from the Releases page, then:
sudo apt install ./freedom-loader_1.x.x_amd64.deb
```
[//]: # (#### Snap &#40;any distro with snapd&#41;)
[//]: # ()
[//]: # (```bash)
[//]: # (sudo snap install freedom-loader)
[//]: # (```)
#### RPM (Fedora/RHEL/openSUSE, without Copr)
```bash
sudo dnf install ./freedom-loader-1.x.y.x86_64.rpm
```
#### AppImage (any distro, no installation needed)
```bash
# Download the latest .AppImage from the Releases page, then:
chmod +x Freedom-Loader-1.x.y.AppImage
./Freedom-Loader-1.x.y.AppImage
```
Once installed via any method, launch Freedom Loader from your application menu, or from a terminal with:
```bash
freedom-loader
```
### File Locations ### File Locations
Downloaded files are stored in: | | Windows | Linux |
``` |---------------|---------------------------------------------------------|--------------------------------------|
C:\Users\[USERNAME]\Downloads\Freedom Loader | **Downloads** | `C:\Users\[USERNAME]\Downloads\Freedom Loader` | `~/Downloads/Freedom Loader` |
``` | **Logs** | `C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\` | `~/.local/share/FreedomLoader/logs/` |
> [!NOTE]
> *You can change it whenever you want. Just click the "Edit" button in the UI.*
Application logs can be found at: > [!NOTE]
``` > The download folder can be changed anytime via the "Edit" button in the UI, on both platforms.
C:\Users\[USERNAME]\AppData\Local\FreedomLoader\logs\
```
## Usage ## Usage
> [!TIP] On Linux, you can start Freedom Loader using `freedom-loader` command
### Basic Download ### Basic Download
1. Launch Freedom Loader 1. Launch Freedom Loader
@@ -138,7 +187,7 @@ The application supports various configuration options:
If you encounter issues: If you encounter issues:
1. Restart the application 1. Restart the application
2. Check the logs in `AppData\Local\FreedomLoader\logs\` 2. Check the logs in `AppData\Local\FreedomLoader\logs\` or `~/.local/share/FreedomLoader/logs`
3. Open the developer console: `CTRL + SHIFT + I` → "Toggle Developer Tools" 3. Open the developer console: `CTRL + SHIFT + I` → "Toggle Developer Tools"
4. Create an issue on GitHub with logs and reproduction steps 4. Create an issue on GitHub with logs and reproduction steps
@@ -164,10 +213,17 @@ If you encounter issues:
Freedom Loader can be configured either through the settings panel in the UI or by directly editing the `config.json` file located in the application data directory. Freedom Loader can be configured either through the settings panel in the UI or by directly editing the `config.json` file located in the application data directory.
### Configuration File Location ### Configuration File Location
#### Windows
``` ```
%APPDATA%\Freedom Loader\config.json %APPDATA%\Freedom Loader\config.json
``` ```
#### Linux
```
~/.config/Freedom Loader/config.json
```
### Available Options ### Available Options
```json ```json
{ {
@@ -185,30 +241,31 @@ Freedom Loader can be configured either through the settings panel in the UI or
"outputTitleCheck": true, "outputTitleCheck": true,
"downloadSystem": true, "downloadSystem": true,
"notifySystem": true, "notifySystem": true,
"enableHardwareAcceleration": true,
"theme": "dark" "theme": "dark"
} }
``` ```
### Configuration Options ### Configuration Options
| Option | Type | Default | Description | | Option | Type | Default | Description |
|--------|------|---------|-------------| |------------------------------|---------|----------|------------------------------------------------------------|
| `autoUpdate` | boolean | `true` | Enable automatic application updates | | `autoUpdate` | boolean | `true` | Enable automatic application updates |
| `discordRPC` | boolean | `true` | Enable Discord Rich Presence integration | | `discordRPC` | boolean | `true` | Enable Discord Rich Presence integration |
| `customTopBar` | boolean | `true` | Use custom application top bar | | `customTopBar` | boolean | `true` | Use custom application top bar |
| `autoCheckInfo` | boolean | `true` | Automatically fetch video information on URL paste | | `autoCheckInfo` | boolean | `true` | Automatically fetch video information on URL paste |
| `addThumbnail` | boolean | `true` | Embed thumbnail in downloaded audio files | | `addThumbnail` | boolean | `true` | Embed thumbnail in downloaded audio files |
| `addMetadata` | boolean | `true` | Add metadata tags to downloaded files | | `addMetadata` | boolean | `true` | Add metadata tags to downloaded files |
| `verboseLogs` | boolean | `false` | Enable detailed logging for debugging | | `verboseLogs` | boolean | `false` | Enable detailed logging for debugging |
| `autoDownloadPlaylist` | boolean | `true` | Automatically download entire playlists | | `autoDownloadPlaylist` | boolean | `true` | Automatically download entire playlists |
| `createPlaylistFolders` | boolean | `true` | Automatically create a folder for a playlist | | `createPlaylistFolders` | boolean | `true` | Automatically create a folder for a playlist |
| `customCodec` | string | `"h264"` | Video codec for encoding (supported: h264, h265, vp9, av1) | | `customCodec` | string | `"h264"` | Video codec for encoding (supported: h264, h265, vp9, av1) |
| `logSystem` | boolean | `true` | Enable application logging | | `logSystem` | boolean | `true` | Enable application logging |
| `outputTitleCheck` | boolean | `true` | Verify output file titles | | `outputTitleCheck` | boolean | `true` | Verify output file titles |
| `downloadSystem` | boolean | `true` | Enable download system | | `downloadSystem` | boolean | `true` | Enable download system |
| `notifySystem` | boolean | `true` | Enable system notifications | | `notifySystem` | boolean | `true` | Enable system notifications |
| `enableHardwareAcceleration` | boolean | `true` | Enable HArdware Acceleration | | `enableHardwareAcceleration` | boolean | `true` | Enable Hardware Acceleration |
| `theme` | string | `dark` | Current Theme Name | | `theme` | string | `dark` | Current Theme Name |
### Supported Codecs ### Supported Codecs
@@ -274,7 +331,7 @@ Freedom-Loader/
│ ├── settingsPanel.css │ ├── settingsPanel.css
│ ├── topbar.css │ ├── topbar.css
│ └── videoinfo.css │ └── videoinfo.css
├── ressources/ # Internal resources (binaries) ├── resources/ # Internal resources (binaries)
├── server/ # Express backend server ├── server/ # Express backend server
│ ├── logger.js │ ├── logger.js
│ ├── server.js │ ├── server.js
@@ -385,7 +442,7 @@ npm run build
This project relies on several external binaries. This project relies on several external binaries.
Some of them are **not included** in the public repository and must be added manually. Some of them are **not included** in the public repository and must be added manually.
You must download the required binaries and place them in the `ressources` folder, using the exact filenames listed below. You must download the required binaries and place them in the `resources` folder, using the exact filenames listed below.
#### Required binaries #### Required binaries
@@ -406,7 +463,7 @@ You must download the required binaries and place them in the `ressources` folde
Final folder structure: Final folder structure:
``` ```
ressources/ resources/
├── deno.exe ├── deno.exe
├── ffmpeg.exe ├── ffmpeg.exe
├── ffprobe.exe ├── ffprobe.exe
@@ -422,7 +479,7 @@ ressources/
- Write clear commit messages - Write clear commit messages
- Test thoroughly before submitting changes - Test thoroughly before submitting changes
- Update documentation when adding features - Update documentation when adding features
- Maintain compatibility with Windows 10+ - Maintain compatibility with Windows 10+ and Linux
## Roadmap ## Roadmap
@@ -444,7 +501,7 @@ Use the GitHub Issues system and include:
- Clear description of the issue - Clear description of the issue
- Steps to reproduce - Steps to reproduce
- Expected vs actual behavior - Expected vs actual behavior
- Relevant logs from `AppData\Local\FreedomLoader\logs\` - Relevant logs from `AppData\Local\FreedomLoader\logs\` or `~/.local/share/FreedomLoader/logs/`
- Screenshots if applicable - Screenshots if applicable
### Feature Requests ### Feature Requests

View File

@@ -5,7 +5,7 @@
We actively maintain and provide security updates for the following versions of Freedom Loader: We actively maintain and provide security updates for the following versions of Freedom Loader:
| Version | Supported | | Version | Supported |
| ------- | ------------------ | |---------|--------------------|
| 1.4.x | :white_check_mark: | | 1.4.x | :white_check_mark: |
| 1.3.x | :white_check_mark: | | 1.3.x | :white_check_mark: |
| < 1.3.0 | :x: | | < 1.3.0 | :x: |

View File

@@ -15,7 +15,7 @@ const { logger } = require("../server/logger");
/** /**
* Configures autoUpdater behavior: * Configures autoUpdater behavior:
* - disables automatic download * - disables automatic download
* - disables automatic install on quit * - disables automatic installation on quit
* (manual user-controlled update flow) * (manual user-controlled update flow)
*/ */
autoUpdater.autoDownload = false; autoUpdater.autoDownload = false;
@@ -51,7 +51,7 @@ function initAutoUpdater(mainWindow) {
}); });
if (response === 0) { if (response === 0) {
autoUpdater.downloadUpdate(); await autoUpdater.downloadUpdate();
} else { } else {
mainWindow?.webContents.executeJavaScript( mainWindow?.webContents.executeJavaScript(
`window.showUpdateBadge && window.showUpdateBadge("${info.version}")` `window.showUpdateBadge && window.showUpdateBadge("${info.version}")`
@@ -96,8 +96,20 @@ function initAutoUpdater(mainWindow) {
* Logs failure and displays an error dialog to user. * Logs failure and displays an error dialog to user.
*/ */
autoUpdater.on("error", (err) => { autoUpdater.on("error", (err) => {
logger.error("Auto update error:", err.message); const msg = err?.message || "";
dialog.showErrorBox("Update Error", err.message);
/**
* If no update is available, I put this because there is no Linux version before 1.6.0
* @type {boolean}
*/
const isNoUpdateAvailable = /404/.test(msg) || /Cannot find latest.*\.yml/i.test(msg);
if (isNoUpdateAvailable) {
logger.warn("Auto update: no update metadata found (probably no previous release), ignoring", msg);
return;
}
logger.error("Auto update error:", msg);
}); });
checkForUpdates(); checkForUpdates();
@@ -108,6 +120,14 @@ function initAutoUpdater(mainWindow) {
* Separated from init for reusability and testability. * Separated from init for reusability and testability.
*/ */
async function checkForUpdates() { async function checkForUpdates() {
if (process.env.SNAP) {
logger.info("Running as Snap, update managed by snapd, skipping electron-updater");
return;
}
if (!require("electron").app.isPackaged) return ;
try { try {
await autoUpdater.checkForUpdates(); await autoUpdater.checkForUpdates();
} catch (err) { } catch (err) {

View File

@@ -25,15 +25,19 @@ function checkNativeDependencies() {
*/ */
const { binaryPaths } = require("../server/helpers/path.helpers"); const { binaryPaths } = require("../server/helpers/path.helpers");
const isWindows = process.platform === "win32";
const addExe = isWindows ? ".exe" : "";
/** /**
* List of required external executables used by the application runtime. * List of required external executables used by the application runtime.
* Each entry defines a binary name and its resolved absolute path. * Each entry defines a binary name and its resolved absolute path.
*/ */
const deps = [ const deps = [
{ name: "yt-dlp.exe", path: binaryPaths.ytDlp }, { name: `yt-dlp${addExe}`, path: binaryPaths.ytDlp },
{ name: "ffmpeg.exe", path: binaryPaths.ffmpeg }, { name: `ffmpeg${addExe}`, path: binaryPaths.ffmpeg },
{ name: "ffprobe.exe", path: binaryPaths.ffprobe }, { name: `ffprobe${addExe}`, path: binaryPaths.ffprobe },
{ name: "deno.exe", path: binaryPaths.deno }, { name: `deno${addExe}`, path: binaryPaths.deno },
]; ];
const missing = deps.filter(d => !fs.existsSync(d.path)); const missing = deps.filter(d => !fs.existsSync(d.path));
@@ -52,7 +56,7 @@ function checkNativeDependencies() {
app.whenReady().then(() => { app.whenReady().then(() => {
dialog.showErrorBox( dialog.showErrorBox(
"Missing dependencies", "Missing dependencies",
`The following files are missing in the 'ressources' folder:\n${list}\n\nThe application will now exit. Try to reinstall.` `The following files are missing in the 'resources' folder:\n${list}\n\nThe application will now exit. Try to reinstall.`
); );
app.quit(); app.quit();
}); });

View File

@@ -12,7 +12,6 @@
const { ipcMain, dialog, shell } = require("electron"); const { ipcMain, dialog, shell } = require("electron");
const fs = require("fs"); const fs = require("fs");
const path = require("path");
const { logger, logDir } = require("../server/logger"); const { logger, logDir } = require("../server/logger");
const { configFeatures, featuresPath } = require("../config"); const { configFeatures, featuresPath } = require("../config");
const { getThemes, reloadThemes } = require("./themeManager"); const { getThemes, reloadThemes } = require("./themeManager");

View File

@@ -62,10 +62,19 @@ function validateDownloadPath(userPath) {
* Required to prevent path traversal or alias bypass. * Required to prevent path traversal or alias bypass.
*/ */
const resolved = fs.realpathSync(path.resolve(userPath)); const resolved = fs.realpathSync(path.resolve(userPath));
if (!fs.existsSync(resolved)) {
fs.mkdirSync(resolved, { recursive: true });
logger.info(`Download folder created: ${resolved}`);
}
const real = fs.realpathSync(resolved);
if (!isSafePath(resolved)) { if (!isSafePath(resolved)) {
throw new Error("Path not allowed: system folders are blocked!"); throw new Error("Path not allowed: system folders are blocked!");
} }
return resolved;
return real;
} }
catch (err) { catch (err) {

View File

@@ -270,7 +270,6 @@ async function loadThemesFromFolder() {
if (fs.statSync(filePath).isDirectory()) { if (fs.statSync(filePath).isDirectory()) {
const theme = await loadThemeFromFolder(filePath, themeId); const theme = await loadThemeFromFolder(filePath, themeId);
if (theme) themes.push(theme); if (theme) themes.push(theme);
continue;
} }
} }

BIN
build/app-icon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

View File

@@ -79,7 +79,7 @@ const { updateYtDlp } = require("./app/ytDlpUpdater");
const { createMainWindow, getMainWindow } = require("./app/windowManager"); const { createMainWindow, getMainWindow } = require("./app/windowManager");
const { registerIpcHandlers } = require("./app/ipcHandlers"); const { registerIpcHandlers } = require("./app/ipcHandlers");
const { createSplashWindow, closeSplashWindow, setSplashProgress } = require("./app/splashManager"); const { createSplashWindow, closeSplashWindow, setSplashProgress } = require("./app/splashManager");
const { userThemesPath, initUserThemes } = require("./server/helpers/path.helpers"); const { userThemesPath, initUserThemes, isWindows } = require("./server/helpers/path.helpers");
/** /**
@@ -116,8 +116,9 @@ app.whenReady().then(async () => {
registerIpcHandlers(getMainWindow); registerIpcHandlers(getMainWindow);
setSplashProgress(2); // Loading themes setSplashProgress(2); // Loading themes
initUserThemes(); initUserThemes();
await initThemes(userThemesPath); await initThemes(userThemesPath);
setSplashProgress(3); // Almost ready setSplashProgress(3); // Almost ready
await createMainWindow(); await createMainWindow();

View File

@@ -1,13 +1,17 @@
{ {
"name": "freedom-loader", "name": "freedom-loader",
"productName": "Freedom Loader", "productName": "Freedom Loader",
"version": "1.5.1", "version": "1.6.0",
"author": "MasterAcnolo", "author": "MasterAcnolo <MasterAcnolo@users.noreply.github.com>",
"description": "Freedom Loader", "description": "Freedom Loader",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"start": "electron --trace-warnings .", "start": "electron --trace-warnings .",
"postinstall": "chmod +x resources/binaries/linux/* 2>/dev/null || true",
"build": "electron-builder", "build": "electron-builder",
"build:win": "electron-builder --win",
"build:linux": "electron-builder --linux",
"build:all": "electron-builder -wl",
"update": "npm update" "update": "npm update"
}, },
"dependencies": { "dependencies": {
@@ -26,7 +30,6 @@
"electron": "^41.2.0", "electron": "^41.2.0",
"electron-builder": "^26.8.1", "electron-builder": "^26.8.1",
"@electron/devtron": "^2.1.1" "@electron/devtron": "^2.1.1"
}, },
"build": { "build": {
"appId": "com.masteracnolo.freedomloader", "appId": "com.masteracnolo.freedomloader",
@@ -35,7 +38,8 @@
{ {
"provider": "github", "provider": "github",
"owner": "MasterAcnolo", "owner": "MasterAcnolo",
"repo": "Freedom-Loader" "repo": "Freedom-Loader",
"releaseType": "draft"
} }
], ],
"files": [ "files": [
@@ -49,54 +53,23 @@
}, },
"asar": true, "asar": true,
"extraResources": [ "extraResources": [
{ { "from": "theme", "to": "theme" },
"from": "theme", { "from": "build/confirm-icon.png", "to": "confirm-icon.png" },
"to": "theme" { "from": "build/banner.bmp", "to": "banner.bmp" },
}, { "from": "build/banner.png", "to": "banner.png" },
{ { "from": "build/error.png", "to": "error.png" },
"from": "build/confirm-icon.png", { "from": "config/config.default.json", "to": "config/config.default.json" }
"to": "confirm-icon.png"
},
{
"from": "build/app-icon.ico",
"to": "app-icon.ico"
},
{
"from": "build/banner.bmp",
"to": "banner.bmp"
},
{
"from": "build/banner.png",
"to": "banner.png"
},
{
"from": "build/error.png",
"to": "error.png"
},
{
"from": "ressources/yt-dlp.exe",
"to": "binaries/yt-dlp.exe"
},
{
"from": "ressources/ffmpeg.exe",
"to": "binaries/ffmpeg.exe"
},
{
"from": "ressources/ffprobe.exe",
"to": "binaries/ffprobe.exe"
},
{
"from": "ressources/deno.exe",
"to": "binaries/deno.exe"
},
{
"from": "config/config.default.json",
"to": "config/config.default.json"
}
], ],
"win": { "win": {
"target": "nsis", "target": "nsis",
"icon": "build/app-icon.ico" "icon": "build/app-icon.ico",
"artifactName": "${productName}-Setup-${version}.${ext}",
"extraResources": [
{ "from": "resources/binaries/win-32/yt-dlp.exe", "to": "binaries/yt-dlp.exe" },
{ "from": "resources/binaries/win-32/ffmpeg.exe", "to": "binaries/ffmpeg.exe" },
{ "from": "resources/binaries/win-32/ffprobe.exe", "to": "binaries/ffprobe.exe" },
{ "from": "resources/binaries/win-32/deno.exe", "to": "binaries/deno.exe" }
]
}, },
"nsis": { "nsis": {
"oneClick": false, "oneClick": false,
@@ -108,6 +81,41 @@
"createDesktopShortcut": true, "createDesktopShortcut": true,
"createStartMenuShortcut": true, "createStartMenuShortcut": true,
"shortcutName": "Freedom Loader" "shortcutName": "Freedom Loader"
},
"linux": {
"target": ["AppImage", "deb","rpm", "snap"],
"category": "Utility",
"icon": "build/app-icon.png",
"extraResources": [
{ "from": "resources/binaries/linux/yt-dlp", "to": "binaries/yt-dlp" },
{ "from": "resources/binaries/linux/ffmpeg", "to": "binaries/ffmpeg" },
{ "from": "resources/binaries/linux/ffprobe", "to": "binaries/ffprobe" },
{ "from": "resources/binaries/linux/deno", "to": "binaries/deno" }
]
},
"deb": {
"fpm": [
"./package/com.masteracnolo.freedomloader.metainfo.xml=/usr/share/metainfo/com.masteracnolo.freedomloader.metainfo.xml"
]
},
"rpm": {
"fpm": [
"./package/com.masteracnolo.freedomloader.metainfo.xml=/usr/share/metainfo/com.masteracnolo.freedomloader.metainfo.xml"
]
},
"snap": {
"confinement": "strict",
"plugs": [
"default",
"home",
"removable-media",
"network",
"audio-playback",
"desktop",
"desktop-legacy",
"x11",
"wayland"
]
} }
} }
} }

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>com.masteracnolo.freedomloader</id>
<name>Freedom Loader</name>
<summary>GUI for yt-dlp to download video and audio with metadata</summary>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0-only</project_license>
<developer id="com.masteracnolo">
<name>MasterAcnolo</name>
</developer>
<description>
<p>
Freedom Loader is a free and open-source graphical interface built
around yt-dlp, the powerful command-line media downloader. It lets
you download video and audio from hundreds of supported websites
without ever touching a terminal, with no ads, no tracking, and
no questionable third-party services.
</p>
<p>
Under the hood, Freedom Loader bundles yt-dlp and ffmpeg directly,
so there is nothing extra to install: paste a link, choose a
format and quality, and the app handles fetching, converting
and tagging the file for you.
</p>
<p>Features include:</p>
<ul>
<li>MP4 video or MP3 audio downloads with embedded metadata and thumbnails</li>
<li>Quality selection from best to specific resolutions (1080p, 720p, 480p)</li>
<li>Codec choice: H.264, H.265, AV1, VP9 and Theora</li>
<li>Playlist downloads with automatic per-playlist folders</li>
<li>Cookie-based access to age-restricted and member-only content</li>
<li>Custom theme system with a web-based theme creator</li>
<li>Real-time download progress with network speed indicator</li>
<li>Optional Discord Rich Presence integration</li>
<li>Automatic application and download-engine updates</li>
</ul>
<p>
DRM-protected content is not supported and cannot be downloaded.
</p>
</description>
<launchable type="desktop-id">freedom-loader.desktop</launchable>
<url type="homepage">https://github.com/MasterAcnolo/Freedom-Loader</url>
<url type="bugtracker">https://github.com/MasterAcnolo/Freedom-Loader/issues</url>
<url type="vcs-browser">https://github.com/MasterAcnolo/Freedom-Loader</url>
<categories>
<category>AudioVideo</category>
<category>Utility</category>
<category>Network</category>
</categories>
<releases>
<release version="1.6.0" date="2026-06-23">
<description>
<p>Latest stable release.</p>
</description>
</release>
</releases>
<content_rating type="oars-1.1"/>
<provides>
<binary>freedom-loader</binary>
</provides>
</component>

View File

@@ -0,0 +1,25 @@
Name: freedom-loader
Version: __VERSION__
Release: 1%{?dist}
Summary: GUI for yt-dlp media downloads
License: GPL-3.0-only
Source0: freedom-loader-__VERSION__.x86_64.rpm
BuildArch: x86_64
Requires: glibc, gtk3, nss, libXScrnSaver
%description
GUI for yt-dlp media downloads.
%install
rpm2cpio %{SOURCE0} | (cd %{buildroot} && cpio -idmv)
mkdir -p %{buildroot}/usr/bin
ln -sf "/opt/Freedom Loader/freedom-loader" %{buildroot}/usr/bin/freedom-loader
%files
/opt/*
/usr/bin/freedom-loader
/usr/share/applications/*
/usr/share/metainfo/*
/usr/share/icons/hicolor
%changelog

View File

@@ -9,6 +9,11 @@ const { contextBridge, ipcRenderer } = require("electron");
*/ */
contextBridge.exposeInMainWorld("electronAPI", { contextBridge.exposeInMainWorld("electronAPI", {
/**
* Return process.platform to renderer
*/
getProcessPlatform: () => process.platform,
/** /**
* Returns the default system download directory. * Returns the default system download directory.
*/ */

View File

@@ -290,6 +290,7 @@
<!-- Scripts --> <!-- Scripts -->
<script src="script/toast.js"></script> <script src="script/toast.js"></script>
<script src="script/customConfiguration.js"></script>
<script src="script/clipboardPaste.js"></script> <script src="script/clipboardPaste.js"></script>
<script src="script/custompath.js"></script> <script src="script/custompath.js"></script>
<script src="script/settingsPanel.js"></script> <script src="script/settingsPanel.js"></script>

View File

@@ -17,9 +17,7 @@ document.addEventListener("DOMContentLoaded", () => {
*/ */
pasteBtn.addEventListener("click", async () => { pasteBtn.addEventListener("click", async () => {
try { try {
const text = await navigator.clipboard.readText(); urlInput.value = await navigator.clipboard.readText();
urlInput.value = text;
// Notify frameworks / listeners bound to input changes // Notify frameworks / listeners bound to input changes
urlInput.dispatchEvent(new Event("input", { bubbles: true })); urlInput.dispatchEvent(new Event("input", { bubbles: true }));

View File

@@ -0,0 +1,27 @@
async function initPlatformRestrictions() {
const platform = await window.electronAPI.getProcessPlatform();
const themeSelect = document.getElementById("themeSelect");
const refreshBtn = document.getElementById("refresh-themes-btn");
/**
* Is the OS windows
* @type {boolean}
*/
const isWindows = platform === "win32";
console.log(isWindows);
// If it's windows, the button is not disabled.
themeSelect.disabled = !isWindows;
refreshBtn.disabled = !isWindows;
themeSelect.classList.toggle("disabled", !isWindows);
refreshBtn.classList.toggle("disabled", !isWindows);
if (!isWindows) {
themeSelect.title = "Themes are only available on Windows";
refreshBtn.title = "Themes are only available on Windows";
}
}

View File

@@ -32,52 +32,52 @@ function applyTheme(theme) {
} }
// CSS variables (design tokens) // CSS variables (design tokens)
root.style.setProperty("--background-color", style.colors?.background || ""); root.style.setProperty("--background-color", style.colors?.background || "#121212");
root.style.setProperty("--default-text-color", style.colors?.text?.default || ""); root.style.setProperty("--default-text-color", style.colors?.text?.default || "#e0e0e0");
root.style.setProperty("--subtitle-color", style.colors?.text?.subtitle || ""); root.style.setProperty("--subtitle-color", style.colors?.text?.subtitle || "#2196f3");
root.style.setProperty("--audio-only-label-color", style.colors?.text?.audioOnly || ""); root.style.setProperty("--audio-only-label-color", style.colors?.text?.audioOnly || "#f5f5f5");
root.style.setProperty("--form-bg-color", style.form?.background || ""); root.style.setProperty("--form-bg-color", style.form?.background || "#1e1e1e");
root.style.setProperty("--form-input-bg-color", style.form?.input?.background || ""); root.style.setProperty("--form-input-bg-color", style.form?.input?.background || "#2a2a2a");
root.style.setProperty("--form-input-border-color", style.form?.input?.border || ""); root.style.setProperty("--form-input-border-color", style.form?.input?.border || "#444444");
root.style.setProperty("--form-input-border-focus-color", style.form?.input?.borderFocus || ""); root.style.setProperty("--form-input-border-focus-color", style.form?.input?.borderFocus || "#2196f3");
root.style.setProperty("--form-input-text-color", style.form?.input?.text || ""); root.style.setProperty("--form-input-text-color", style.form?.input?.text || "#f5f5f5");
root.style.setProperty("--form-input-placeholder-color", style.form?.input?.placeholder || ""); root.style.setProperty("--form-input-placeholder-color", style.form?.input?.placeholder || "#888888");
root.style.setProperty("--form-button-bg-color", style.form?.button?.background || ""); root.style.setProperty("--form-button-bg-color", style.form?.button?.background || "#2196f3");
root.style.setProperty("--form-button-text-color", style.form?.button?.text || ""); root.style.setProperty("--form-button-text-color", style.form?.button?.text || "#ffffff");
root.style.setProperty("--form-button-bg-hover-color", style.form?.button?.hover || ""); root.style.setProperty("--form-button-bg-hover-color", style.form?.button?.hover || "#1976d2");
root.style.setProperty("--paste-button-icon-color", style.form?.pasteButtonIcon || ""); root.style.setProperty("--paste-button-icon-color", style.form?.pasteButtonIcon || "#ffffff");
root.style.setProperty("--checkbox-unchecked-bg-color", style.checkbox?.background?.unchecked || ""); root.style.setProperty("--checkbox-unchecked-bg-color", style.checkbox?.background?.unchecked || "#555555");
root.style.setProperty("--checkbox-checked-bg-color", style.checkbox?.background?.checked || ""); root.style.setProperty("--checkbox-checked-bg-color", style.checkbox?.background?.checked || "#2196f3");
root.style.setProperty("--checkbox-checkmark-border-color", style.checkbox?.checkmarkBorder || ""); root.style.setProperty("--checkbox-checkmark-border-color", style.checkbox?.checkmarkBorder || "#ffffff");
root.style.setProperty("--checkbox-pulse-color", style.checkbox?.pulse || ""); root.style.setProperty("--checkbox-pulse-color", style.checkbox?.pulse || "rgba(33, 150, 243, 0.5)");
root.style.setProperty("--download-status-color", style.download?.status || ""); root.style.setProperty("--download-status-color", style.download?.status || "#2196f3");
root.style.setProperty("--progress-bar-bg-color", style.progressBar?.background || ""); root.style.setProperty("--progress-bar-bg-color", style.progressBar?.background || "#444444");
root.style.setProperty("--progress-bar-fill-color", style.progressBar?.fill || ""); root.style.setProperty("--progress-bar-fill-color", style.progressBar?.fill || "#2196f3");
root.style.setProperty("--video-info-bg-color", style.videoInfo?.background || ""); root.style.setProperty("--video-info-bg-color", style.videoInfo?.background || "#1a1a1a");
root.style.setProperty("--video-info-text-color", style.videoInfo?.text || ""); root.style.setProperty("--video-info-text-color", style.videoInfo?.text || "#e0e0e0");
root.style.setProperty("--video-info-heading-color", style.videoInfo?.heading || ""); root.style.setProperty("--video-info-heading-color", style.videoInfo?.heading || "#2196f3");
root.style.setProperty("--video-info-list-color", style.videoInfo?.list || ""); root.style.setProperty("--video-info-list-color", style.videoInfo?.list || "#cccccc");
root.style.setProperty("--video-info-list-strong-color", style.videoInfo?.strong || ""); root.style.setProperty("--video-info-list-strong-color", style.videoInfo?.strong || "#ffffff");
root.style.setProperty("--video-info-link-color", style.videoInfo?.link || ""); root.style.setProperty("--video-info-link-color", style.videoInfo?.link || "#2196f3");
root.style.setProperty("--video-info-link-hover-color", style.videoInfo?.linkHover || ""); root.style.setProperty("--video-info-link-hover-color", style.videoInfo?.linkHover || "#1976d2");
root.style.setProperty("--playlist-background-color", style.playlist?.background || ""); root.style.setProperty("--playlist-background-color", style.playlist?.background || "#303030");
root.style.setProperty("--settings-button-bg-color", style.settings?.button?.background || ""); root.style.setProperty("--settings-button-bg-color", style.settings?.button?.background || "#1e1e1e");
root.style.setProperty("--settings-button-text-color", style.settings?.button?.text || ""); root.style.setProperty("--settings-button-text-color", style.settings?.button?.text || "#e0e0e0");
root.style.setProperty("--open-theme-button-bg-color", style.settings?.openThemeButton?.background || ""); root.style.setProperty("--open-theme-button-bg-color", style.settings?.openThemeButton?.background || "#2196f3");
root.style.setProperty("--open-theme-button-text-color", style.settings?.openThemeButton?.text || ""); root.style.setProperty("--open-theme-button-text-color", style.settings?.openThemeButton?.text || "#ffffff");
root.style.setProperty("--open-json-button-bg-color", style.settings?.openJsonButton?.background || style.settings?.button?.background || ""); root.style.setProperty("--open-json-button-bg-color", style.settings?.openJsonButton?.background || style.settings?.button?.background || "#1e1e1e");
root.style.setProperty("--open-json-button-text-color", style.settings?.openJsonButton?.text || style.settings?.button?.text || ""); root.style.setProperty("--open-json-button-text-color", style.settings?.openJsonButton?.text || style.settings?.button?.text || "#e0e0e0");
root.style.setProperty("--settings-modal-bg-color", style.settings?.background?.modal || ""); root.style.setProperty("--settings-modal-bg-color", style.settings?.background?.modal || "#1e1e1e");
root.style.setProperty("--settings-section-bg-color", style.settings?.background?.section || ""); root.style.setProperty("--settings-section-bg-color", style.settings?.background?.section || "#2a2a2a");
root.style.setProperty("--settings-text-color", style.settings?.text || ""); root.style.setProperty("--settings-text-color", style.settings?.text || "#e0e0e0");
root.style.setProperty("--settings-subtitle-color", style.settings?.subtitle || ""); root.style.setProperty("--settings-subtitle-color", style.settings?.subtitle || "#aaaaaa");
const subtitleEl = document.getElementById("subtitle"); const subtitleEl = document.getElementById("subtitle");
if (subtitleEl) subtitleEl.textContent = theme.subtitle || theme.name; if (subtitleEl) subtitleEl.textContent = theme.subtitle || theme.name;
@@ -116,7 +116,102 @@ function populateThemeSelect(themes) {
*/ */
async function initThemes() { async function initThemes() {
loadedThemes = await window.electronAPI.getThemes(); loadedThemes = await window.electronAPI.getThemes();
if (!loadedThemes.length) return;
if (!loadedThemes.length) {
const defaultTheme = {
id: "dark",
name: "Dark",
subtitle: "Darkness is my ally",
style: {
background: {
size: "cover",
position: "center",
attachment: "fixed"
},
colors: {
background: "#121212",
text: {
default: "#e0e0e0",
subtitle: "#2196f3",
audioOnly: "#f5f5f5"
}
},
form: {
background: "#1e1e1e",
input: {
background: "#2a2a2a",
border: "#444444",
borderFocus: "#2196f3",
text: "#f5f5f5",
placeholder: "#888888"
},
button: {
background: "#2196f3",
text: "#ffffff",
hover: "#1976d2"
},
pasteButtonIcon: "#ffffff"
},
checkbox: {
background: {
unchecked: "#555555",
checked: "#2196f3"
},
checkmarkBorder: "#ffffff",
pulse: "rgba(33, 150, 243, 0.5)"
},
download: {
status: "#2196f3"
},
progressBar: {
background: "#444444",
fill: "#2196f3"
},
videoInfo: {
background: "#1a1a1a",
text: "#e0e0e0",
heading: "#2196f3",
list: "#cccccc",
strong: "#ffffff",
link: "#2196f3",
linkHover: "#1976d2"
},
playlist: {
background: "#303030"
},
settings: {
button: {
background: "#1e1e1e",
text: "#e0e0e0"
},
openThemeButton: {
background: "#2196f3",
text: "#ffffff"
},
openJsonButton: {
background: "#2196f3",
text: "#ffffff"
},
background: {
modal: "#1e1e1e",
section: "#2a2a2a"
},
text: "#e0e0e0",
subtitle: "#aaaaaa"
}
}
};
loadedThemes = [defaultTheme];
}
populateThemeSelect(loadedThemes); populateThemeSelect(loadedThemes);

View File

@@ -271,6 +271,6 @@ async function init() {
}) })
}; }
init(); init();

View File

@@ -16,6 +16,14 @@
@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');
/* Mark element as disabled */
.disabled {
pointer-events: none;
opacity: 0.5;
filter: grayscale(40%);
cursor: not-allowed;
}
/* Because why not */ /* Because why not */
#reference{ #reference{
position: fixed; position: fixed;

BIN
resources/binaries/linux/yt-dlp Executable file

Binary file not shown.

20
scripts/make-copr-srpm.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
VERSION=$(node -p "require('$ROOT_DIR/package.json').version")
mkdir -p "$ROOT_DIR/srpm-out"
TMP_DIR="$ROOT_DIR/.copr-build-tmp"
mkdir -p "$TMP_DIR"
TMP_SPEC="$TMP_DIR/freedom-loader.spec"
sed "s/__VERSION__/$VERSION/g" "$ROOT_DIR/package/freedom-loader.spec" > "$TMP_SPEC"
rpmbuild -bs "$TMP_SPEC" \
--define "_sourcedir $ROOT_DIR/dist" \
--define "_srcrpmdir $ROOT_DIR/srpm-out"
rm -rf "$TMP_DIR"
echo "SRPM généré : $ROOT_DIR/srpm-out/freedom-loader-$VERSION-1*.src.rpm"

View File

@@ -22,7 +22,8 @@ const { logger } = require("../logger");
* @returns {string} The detected browser identifier. * @returns {string} The detected browser identifier.
*/ */
function getUserBrowser() { function getUserBrowser() {
const userProfile = os.homedir(); //const userProfile = os.homedir();
const firefoxPath = getFirefoxProfilePath();
/** /**
* Browsers supported by yt-dlp cookie extraction. * Browsers supported by yt-dlp cookie extraction.
@@ -32,7 +33,7 @@ function getUserBrowser() {
* once compatibility has been verified. * once compatibility has been verified.
*/ */
const browsers = [ const browsers = [
{ name: "firefox", path: path.join(userProfile, "AppData", "Roaming", "Mozilla", "Firefox", "Profiles") }, { name: "firefox", path: firefoxPath },
// { name: "chrome", path: path.join(userProfile, "AppData", "Local", "Google", "Chrome", "User Data", "Default") }, // { name: "chrome", path: path.join(userProfile, "AppData", "Local", "Google", "Chrome", "User Data", "Default") },
// { name: "brave", path: path.join(userProfile, "AppData", "Local", "BraveSoftware", "Brave-Browser", "User Data", "Default") }, // { name: "brave", path: path.join(userProfile, "AppData", "Local", "BraveSoftware", "Brave-Browser", "User Data", "Default") },
// { name: "edge", path: path.join(userProfile, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default") }, // { name: "edge", path: path.join(userProfile, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default") },
@@ -44,7 +45,7 @@ function getUserBrowser() {
// Search for the first available browser profile. // Search for the first available browser profile.
for (const browser of browsers) { for (const browser of browsers) {
if (fs.existsSync(browser.path)) { if (browser.path && fs.existsSync(browser.path)) {
logger.info(`Browser found: ${browser.name}`); logger.info(`Browser found: ${browser.name}`);
return browser.name; return browser.name;
} }
@@ -52,11 +53,50 @@ function getUserBrowser() {
// No supported browser found => Notify User // No supported browser found => Notify User
logger.warn("No supported browser found on the system"); logger.warn("No supported browser found on the system");
notify.notifyFirefoxBrowserMissing();
// If you somehow managed to live without Firefox and need help installing it. - Don't applied to my Linux chad
if (process.platform === "win32") {
notify.notifyFirefoxBrowserMissing();
}
// Fallback to Firefox and let yt-dlp handle the error gracefully. // Fallback to Firefox and let yt-dlp handle the error gracefully.
// This prevents the application from crashing // This prevents the application from crashing
return "firefox"; return "firefox";
} }
function getFirefoxProfilePath() {
const home = os.homedir();
if (process.platform === "win32") {
return path.join(
home,
"AppData",
"Roaming",
"Mozilla",
"Firefox",
"Profiles"
);
}
if (process.platform === "linux") {
return path.join(
"usr",
"bin",
"firefox"
);
}
if (process.platform === "darwin") {
return path.join(
home,
"Library",
"Application Support",
"Firefox",
"Profiles"
);
}
return null;
}
module.exports = getUserBrowser module.exports = getUserBrowser

View File

@@ -1,18 +1,47 @@
const path = require("path"); const path = require("path");
const fs = require("fs"); const fs = require("fs");
const os = require("os");
const { app } = require("electron"); const { app } = require("electron");
const config = require("../../config.js"); const config = require("../../config.js");
const { logger } = require("../logger.js"); /**
* Is the OS windows
*/
const isWindows = process.platform === 'win32'
// Centralized resource paths /**
const resourcesPath = config.devMode * On windows the binaries are name.exe on Linux/macOS no
? path.join(__dirname, "../../ressources") */
const ext = isWindows ? '.exe' : ''
/** Centralized resource paths */
const resourcesPath = config.devMode
? path.join(__dirname, "../../resources")
: process.resourcesPath; : process.resourcesPath;
// Default download folder (centralized) /** Default download folder (centralized) */
const defaultDownloadFolder = path.join(os.homedir(), "Downloads", "Freedom Loader"); const defaultDownloadFolder = path.join(app.getPath("downloads"), "Freedom Loader");
/**
* Current platform:
* win32 | linux | darwin
*/
const platform = process.platform;
/**
* Dev source tree uses platform-named subfolders (matches resources/binaries/{win-32,linux,darwin}).
* Packaged builds flatten everything directly under binaries/, since each
* platform build only ships its own binaries — no subfolder needed.
*/
const devPlatformFolder = {
win32: "win-32",
linux: "linux",
darwin: "darwin"
}[platform];
const binariesPath = config.devMode
? path.join(resourcesPath, "binaries", devPlatformFolder)
: path.join(resourcesPath, "binaries");
/** /**
* Runtime-resolved binary paths. * Runtime-resolved binary paths.
@@ -24,7 +53,10 @@ let ffmpegPath;
let denoPath; let denoPath;
/** Source binary bundled inside application resources (used for first-time copy) */ /** Source binary bundled inside application resources (used for first-time copy) */
const sourceYtDlp = path.join(resourcesPath, "binaries","yt-dlp.exe"); const sourceYtDlp = path.join(
binariesPath,
`yt-dlp${ext}`
);
/** /**
* Resolve binary locations depending on environment: * Resolve binary locations depending on environment:
@@ -32,16 +64,47 @@ const sourceYtDlp = path.join(resourcesPath, "binaries","yt-dlp.exe");
* - prod: extracted userData + packaged resources * - prod: extracted userData + packaged resources
*/ */
if (config.devMode) { if (config.devMode) {
userYtDlp = path.join(__dirname, "../../ressources/yt-dlp.exe"); userYtDlp = path.join(
ffmpegPath = path.join(__dirname, "../../ressources/"); // <- has ffmpeg.exe and ffprobe.exe binariesPath,
denoPath = path.join(__dirname, "../../ressources/deno.exe"); `yt-dlp${ext}`
} else { );
userYtDlp = path.join(app.getPath("userData"),"yt-dlp.exe");
ffmpegPath = path.join(resourcesPath, "binaries","ffmpeg.exe");
denoPath = path.join(resourcesPath, "binaries","deno.exe");
if (!fs.existsSync(userYtDlp)) { ffmpegPath = path.join(
binariesPath,
`ffmpeg${ext}`
);
denoPath = path.join(
binariesPath,
`deno${ext}`
);
} else {
userYtDlp = path.join(
app.getPath("userData"),
`yt-dlp${ext}`
);
ffmpegPath = path.join(
binariesPath,
`ffmpeg${ext}`
);
denoPath = path.join(
binariesPath,
`deno${ext}`
);
if (!fs.existsSync(userYtDlp) && fs.existsSync(sourceYtDlp)) {
fs.copyFileSync(sourceYtDlp, userYtDlp); fs.copyFileSync(sourceYtDlp, userYtDlp);
// Linux/macOS executable permission
if (!isWindows) {
try {
fs.chmodSync(userYtDlp, 0o755);
} catch (err) {
getLogger().warn(`Failed to chmod yt-dlp: ${err.message}`);
}
}
} }
} }
@@ -60,10 +123,25 @@ const iconPaths = {
* Used mainly for verification and diagnostics. * Used mainly for verification and diagnostics.
*/ */
const binaryPaths = { const binaryPaths = {
ytDlp: path.join(resourcesPath, "binaries", "yt-dlp.exe"), ytDlp: path.join(
ffmpeg: path.join(resourcesPath, "binaries", "ffmpeg.exe"), binariesPath,
ffprobe: path.join(resourcesPath, "binaries", "ffprobe.exe"), `yt-dlp${ext}`
deno: path.join(resourcesPath, "binaries", "deno.exe") ),
ffmpeg: path.join(
binariesPath,
`ffmpeg${ext}`
),
ffprobe: path.join(
binariesPath,
`ffprobe${ext}`
),
deno: path.join(
binariesPath,
`deno${ext}`
)
}; };
/** /**
@@ -85,42 +163,38 @@ function initUserThemes() {
try { try {
if (!fs.existsSync(userThemesPath)) { if (!fs.existsSync(userThemesPath)) {
fs.mkdirSync(userThemesPath, { recursive: true }); fs.mkdirSync(userThemesPath, { recursive: true });
// Ensure folder is writable
fs.chmodSync(userThemesPath, 0o777); fs.chmodSync(userThemesPath, 0o777);
logger.info("Created user themes directory"); getLogger().info("Created user themes directory");
} }
// Copy default themes from resources to user directory if they don't exist
if (fs.existsSync(defaultThemesSourcePath)) { if (fs.existsSync(defaultThemesSourcePath)) {
const defaultThemes = fs.readdirSync(defaultThemesSourcePath); const defaultThemes = fs.readdirSync(defaultThemesSourcePath);
for (const theme of defaultThemes) { for (const theme of defaultThemes) {
const srcPath = path.join(defaultThemesSourcePath, theme); const srcPath = path.join(defaultThemesSourcePath, theme);
const destPath = path.join(userThemesPath, theme); const destPath = path.join(userThemesPath, theme);
if (!fs.existsSync(destPath)) { if (!fs.existsSync(destPath)) {
try { try {
copyDirectory(srcPath, destPath); if (fs.statSync(srcPath).isDirectory()) {
fs.chmodSync(destPath, 0o777); copyDirectory(srcPath, destPath);
logger.info(`Copied default theme: ${theme}`); fs.chmodSync(destPath, 0o777);
} else {
fs.copyFileSync(srcPath, destPath);
fs.chmodSync(destPath, 0o666);
}
getLogger().info(`Copied default theme: ${theme}`);
} catch (copyErr) { } catch (copyErr) {
logger.warn(`Failed to copy theme ${theme}: ${copyErr.message}. Theme folder will be created, add theme files manually.`); getLogger().warn(`Failed to copy theme ${theme}: ${copyErr.message}`);
try {
fs.mkdirSync(destPath, { recursive: true });
fs.chmodSync(destPath, 0o777);
} catch (mkdirErr) {
logger.warn(`Could not create theme directory ${theme}: ${mkdirErr.message}`);
}
} }
} }
} }
} else { } else {
logger.warn(`Default themes source path not found: ${defaultThemesSourcePath}`); getLogger().warn(`Default themes source path not found: ${defaultThemesSourcePath}`);
} }
} catch (err) { } catch (err) {
logger.error(`Failed to initialize user themes: ${err.message}`); getLogger().error(`Failed to initialize user themes: ${err.message}`);
} }
} }
/** /**
* Recursively copies a directory and its contents. * Recursively copies a directory and its contents.
* *
@@ -131,14 +205,18 @@ function initUserThemes() {
function copyDirectory(src, dest) { function copyDirectory(src, dest) {
try { try {
if (!fs.existsSync(dest)) { if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true, mode: 0o777 }); fs.mkdirSync(dest, {
recursive: true,
mode: 0o777
});
} }
const files = fs.readdirSync(src); const files = fs.readdirSync(src);
for (const file of files) { for (const file of files) {
const srcFile = path.join(src, file); const srcFile = path.join(src, file);
const destFile = path.join(dest, file); const destFile = path.join(dest, file);
if (fs.statSync(srcFile).isDirectory()) { if (fs.statSync(srcFile).isDirectory()) {
copyDirectory(srcFile, destFile); copyDirectory(srcFile, destFile);
} else { } else {
@@ -151,8 +229,24 @@ function copyDirectory(src, dest) {
} }
// Runtime validation of critical binaries // Runtime validation of critical binaries
if (!userYtDlp){ logger.error("Missing yt-dlp binary")} if (!userYtDlp) {
if (!ffmpegPath){ logger.error("Missing ffmpeg binary")} getLogger().error("Missing yt-dlp binary");
if (!denoPath){ logger.error("Missing deno binary")} }
module.exports = { userYtDlp, ffmpegPath, denoPath, iconPaths, binaryPaths, resourcesPath, defaultDownloadFolder, userThemesPath, initUserThemes }; if (!ffmpegPath) {
getLogger().error("Missing ffmpeg binary");
}
if (!denoPath) {
getLogger().error("Missing deno binary");
}
/**
* Helper to lazy load logger, avoid circular import
* @returns {winston.Logger}
*/
function getLogger() {
return require("../logger.js").logger;
}
module.exports = { userYtDlp, ffmpegPath, denoPath, iconPaths, binaryPaths, resourcesPath, defaultDownloadFolder, userThemesPath, initUserThemes, isWindows };

View File

@@ -4,9 +4,10 @@ const fs = require("fs");
const path = require("path"); const path = require("path");
const os = require("os"); const os = require("os");
const config = require("../config"); const config = require("../config");
const {isWindows} = require("./helpers/path.helpers");
// Logs folder in Windows // Logs folder in Windows
const logDir = path.join(os.homedir(), "AppData", "Local", "FreedomLoader", "logs"); const logDir = isWindows ? path.join(os.homedir(), "AppData", "Local", "FreedomLoader", "logs") : path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), ".local", "share"), "FreedomLoader", "logs");
// Create "Logs" folder if needed // Create "Logs" folder if needed
try { try {

BIN
theme/dark.zip Normal file

Binary file not shown.

BIN
theme/light.zip Normal file

Binary file not shown.