e9de913b47
* fix imports
remove unused imports
use full import path
sort import (1. Standard Library, 2. Third-Party, 3. Local) in alphabetic.
* - remove: Method 3 from src.core.ytsage_downlader:cleanup_subtitle_file
- it could delete the subtitle file of other movies if it present in same directory as it scane recursively.
- refactor: migrate from os.path to pathlib.Path for path handling
- Replaced os.path methods with pathlib.Path to improve readability,
- avoid repeatation.
- cross-platform compatibility, and maintain cleaner code.
- improve: enhance code readability
- Standardized string literals to use double quotes for consistency
- Removed unnecessary spaces to maintain cleaner formatting
- Applied code formatting for better readability and maintainability
* - add: ytsage_constants.py file for one place to store all constants.
- imporve: return type hint for function.
- remove: src/gui/ytsage_gui_dialogs.py file to avoid repetation
- src/gui/dialogs is renamed to src/gui/ytsage_gui_dialogs for same naming convection. (future import will remains same)
- use of src/gui/ytsage_gui_dialogs/__init__.py to import the dilogs modules.
- change: variable self.parent to self._parent so it does not overwrite the parent()
- add type hint checking.
* - refactor: QMetaObject.invokeMethod to Signal
- I encounter error with incokeMethod. Could not solve it.
- So, Changed it to Signal to match app code language.
- implement: the ytsage_constants.py to code
- remove: unnecessary logic
- remove: repetitive code logic.
- update: yt-dlp logic for src\gui\ytsage_gui_dialogs\ytsage_dialogs_update:_update_binary
- yt-dlp update logic will use `yt-dlp -U`
* refactor: remove unused imports and streamline code formatting across multiple files
* - **refactor: drop `pygame` in favor of built-in `PySide6` sound**
- Removed `pygame` dependency (too heavy just for notifications).
- Replaced with `QSoundEffect`, which is lightweight and built into `PySide6`.
- Dropped `pygame.mixer` + threading → Qt handles async playback.
- Implemented sound playback with `QUrl.fromLocalFile()` and `.play()`.
- Added `setVolume(0.9)` as a configurable example.
- Converted notification sound from `.mp3` to `.wav` (only format supported).
* **refactor(utils): simplify logger module**
- Moved `logger` to `src.utils`
- Removed unnecessary import checks (logger is always available)
- Replaced `raise` statements with error logging to prevent crashes
- Use `logger.exception()` in `except` blocks to capture traceback (logged as error)
**style: remove redundant str() in f-strings**
- Dropped explicit `str()` calls inside f-strings
- f-strings already call `str()` under the hood
**chore: add type hints for GUI mixins**
- Added type hints for `FormatTableMixin` (`src.gui.ytsage_gui_format_table`)
- Added type hints for `VideoInfoMixin` (`src.gui.ytsage_gui_video_info`)
- Improves autocomplete and type safety in IDEs
* - **introduce the `ytsage_config_manager.py` module to manage app setting.**
- Loads settings from a JSON config file (`APP_CONFIG_FILE`).
- Creates the config file with default values if missing or corrupt.
- Retrieves, sets, and deletes settings using simple dot-separated keys.
- Provides safe error handling with logging instead of raising exceptions.
- Persists updates back to disk automatically.
- **Usage**
```python
from src.utils.ytsage_config_manager import ConfigManager
download_path = ConfigManager.get("download_path")
ConfigManager.set("download_path", "D:/Downloads")
last_check = ConfigManager.get("cached_versions.ytdlp.last_check")
ConfigManager.delete("cached_versions.ffmpeg.path")
```
* **Refactore: pkg_resources with importlib.metadata.version**
- UserWarning: pkg_resources is deprecated as an API.
- See https://setuptools.pypa.io/en/latest/pkg_resources.html.
- The pkg_resources package is slated for removal as early as 2025-11-30.
* **refactore: notification sound**
- `QSoundEffect` is chnaged back to `pyglet` as per mainter `@oop7` choise.
- simplify the logic.
* remove: import check, it should always work.
* **fix: runtime error**
- yt_dlp moved from `--excludes` to `--packages` in `build-windows.yml`
- In frozen build, logger will not log to consol. insted will log to file.
- In frozen build, `app_dir` is next to `.exe` file.
* chang back to checking import for ytdlp
* Bump version to 4.8.1
Update version references from 4.8.0b to 4.8.1 in __init__.py, main app, and About dialog to reflect the new release.
* Update asset paths in build workflows
Changed asset inclusion and screenshot removal paths from 'assets' to 'lib/assets' in Linux, macOS, and Windows build workflows to reflect new directory structure and ensure screenshots are excluded from packaged builds.
* Bump version to 4.8.2
Update version references from 4.8.1 to 4.8.2 in source files and documentation to prepare for a new patch release.
* Fix asset include path in Windows build workflow
Corrects the syntax for including asset files in the build-windows.yml workflow by changing 'assets,lib/assets' to 'assets=lib/assets'. This ensures assets are properly mapped during the build process.
* Update release tag examples in CI/CD README
Changed the example git tag commands from v4.8.0 and v4.8.2 to v4.8.1 for consistency in the CI/CD documentation.
* Fix include-files mapping in Windows build workflow
Changed the cx_Freeze --include-files argument from '=' to ':' for source:destination mapping in build-windows.yml. This resolves an issue where '=' was treated as a literal path, ensuring assets are correctly copied to the destination directory.
* Remove redundant comments in build-windows workflow
Deleted comments explaining the colon usage for source:destination mapping in the cx_Freeze CLI, as the mapping is already clear from the context.
* Refactor Windows build to use cx_Freeze setup script
Replaces direct cx_Freeze CLI calls with dynamically generated setup scripts for both standard and FFmpeg builds. This improves maintainability and flexibility of build configuration in the GitHub Actions workflow.
* Bump version to 4.8.3
Updated version references from 4.8.2 to 4.8.3 in __init__.py, main app, and About dialog to reflect the new release.
* revert(build): move yt_dlp from --packages to --excludes in build-windows.yml
---------
Co-authored-by: Your Name <mohamed.mohamed112@ai.mnu.edu.eg>
318 lines
14 KiB
YAML
318 lines
14 KiB
YAML
name: Build Windows Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*'
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
env:
|
|
PYTHON_VERSION: '3.13.6'
|
|
|
|
jobs:
|
|
build-windows:
|
|
runs-on: windows-latest
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Extract version from tag
|
|
id: get_version
|
|
shell: powershell
|
|
run: |
|
|
$tag = "${{ github.ref_name }}"
|
|
$version = $tag -replace '^v', ''
|
|
Write-Host "Extracted version: $version"
|
|
echo "VERSION=$version" >> $env:GITHUB_OUTPUT
|
|
|
|
- name: Setup Python
|
|
uses: actions/setup-python@v4
|
|
with:
|
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
|
|
- name: Cache Python dependencies
|
|
uses: actions/cache@v3
|
|
with:
|
|
path: |
|
|
venv
|
|
~\AppData\Local\pip\Cache
|
|
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-pip-
|
|
|
|
- name: Create virtual environment and install dependencies
|
|
shell: powershell
|
|
run: |
|
|
python -m venv venv
|
|
.\venv\Scripts\Activate.ps1
|
|
python -m pip install --upgrade pip
|
|
pip install --no-cache-dir -r requirements.txt
|
|
pip install --no-cache-dir cx_Freeze
|
|
pip install --no-cache-dir yt-dlp
|
|
|
|
- name: Prepare build variables
|
|
shell: powershell
|
|
run: |
|
|
$version = "${{ steps.get_version.outputs.VERSION }}"
|
|
echo "VERSION=$version" >> $env:GITHUB_ENV
|
|
Write-Host "Prepared build variables for version: $version"
|
|
|
|
- name: Create cx_Freeze setup script
|
|
shell: powershell
|
|
run: |
|
|
# Create setup script for cx_Freeze
|
|
New-Item -Path "setup_cxfreeze.py" -ItemType File -Force
|
|
Add-Content -Path "setup_cxfreeze.py" -Value "import os"
|
|
Add-Content -Path "setup_cxfreeze.py" -Value "from cx_Freeze import setup, Executable"
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ""
|
|
Add-Content -Path "setup_cxfreeze.py" -Value 'version = os.environ.get("VERSION", "0.0.0")'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ""
|
|
Add-Content -Path "setup_cxfreeze.py" -Value "build_exe_options = dict("
|
|
Add-Content -Path "setup_cxfreeze.py" -Value " optimize=2,"
|
|
Add-Content -Path "setup_cxfreeze.py" -Value " packages=["
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtCore",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtGui",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtWidgets",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "requests",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PIL",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "packaging",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "markdown",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "pyglet",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "loguru",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "setuptools",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value " ],"
|
|
Add-Content -Path "setup_cxfreeze.py" -Value " excludes=["
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtBluetooth",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtNetwork",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtOpenGL",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtPrintSupport",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtSvg",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtTest",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtXml",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtSql",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtHelp",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtMultimedia",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtQml",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtQuick",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PySide6.QtWebEngineCore",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PIL.ImageDraw",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "PIL.ImageFont",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "numpy",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "scipy",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "wx",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "pandas",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "tkinter",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "yt_dlp",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "unittest",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "test",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' "tests",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value " ],"
|
|
Add-Content -Path "setup_cxfreeze.py" -Value " include_files=["
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' ("src", "src"),'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' ("assets", "lib/assets"),'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value " ],"
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ")"
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ""
|
|
Add-Content -Path "setup_cxfreeze.py" -Value "executables = ["
|
|
Add-Content -Path "setup_cxfreeze.py" -Value " Executable("
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' script="main.py",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' target_name=f"YTSage-v{version}.exe",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' base="Win32GUI",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' icon="assets/branding/icons/YTSage.ico",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value " )"
|
|
Add-Content -Path "setup_cxfreeze.py" -Value "]"
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ""
|
|
Add-Content -Path "setup_cxfreeze.py" -Value "setup("
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' name="YTSage",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value " version=version,"
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' description="YTSage",'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ' options={"build_exe": build_exe_options},'
|
|
Add-Content -Path "setup_cxfreeze.py" -Value " executables=executables,"
|
|
Add-Content -Path "setup_cxfreeze.py" -Value ")"
|
|
|
|
- name: Build Standard Version (ZIP)
|
|
shell: powershell
|
|
run: |
|
|
.\venv\Scripts\Activate.ps1
|
|
$version = "$env:VERSION"
|
|
Write-Host "Building Standard Version v$version ..."
|
|
|
|
# Clean previous build artifacts
|
|
if (Test-Path "build\exe.*") { Remove-Item "build\exe.*" -Recurse -Force }
|
|
if (Test-Path "build\bdist.*") { Remove-Item "build\bdist.*" -Recurse -Force }
|
|
if (Test-Path "dist") { Remove-Item "dist" -Recurse -Force }
|
|
|
|
# Build executable using setup script
|
|
python setup_cxfreeze.py build_exe --build-exe "dist\YTSage"
|
|
|
|
# Remove screenshots folder to reduce build size
|
|
if (Test-Path "dist\YTSage\lib\assets\branding\screenshots") {
|
|
Remove-Item "dist\YTSage\lib\assets\branding\screenshots" -Recurse -Force
|
|
Write-Host "Removed screenshots folder from standard build"
|
|
}
|
|
|
|
- name: Setup FFmpeg for bundle
|
|
shell: powershell
|
|
run: |
|
|
Write-Host "Setting up FFmpeg for bundled version..."
|
|
|
|
# Create ffmpeg directory
|
|
New-Item -ItemType Directory -Path "ffmpeg-temp" -Force
|
|
|
|
# Download FFmpeg
|
|
$ffmpegUrl = "https://github.com/GyanD/codexffmpeg/releases/download/7.1.1/ffmpeg-7.1.1-full_build.zip"
|
|
Write-Host "Downloading FFmpeg from: $ffmpegUrl"
|
|
Invoke-WebRequest -Uri $ffmpegUrl -OutFile "ffmpeg-temp\ffmpeg.zip"
|
|
|
|
# Extract FFmpeg
|
|
Expand-Archive -Path "ffmpeg-temp\ffmpeg.zip" -DestinationPath "ffmpeg-temp" -Force
|
|
|
|
# Find the extracted directory (GyanD ffmpeg has specific naming)
|
|
$ffmpegDir = Get-ChildItem -Path "ffmpeg-temp" -Directory | Where-Object { $_.Name -like "ffmpeg-*" } | Select-Object -First 1
|
|
|
|
if ($ffmpegDir) {
|
|
# Set environment variable for the build
|
|
$ffmpegBinPath = Join-Path $ffmpegDir.FullName "bin"
|
|
Write-Host "FFmpeg binaries found at: $ffmpegBinPath"
|
|
echo "FFMPEG_PATH=$ffmpegBinPath" >> $env:GITHUB_ENV
|
|
|
|
# Verify files exist
|
|
$ffmpegExe = Join-Path $ffmpegBinPath "ffmpeg.exe"
|
|
$ffprobeExe = Join-Path $ffmpegBinPath "ffprobe.exe"
|
|
$ffplayExe = Join-Path $ffmpegBinPath "ffplay.exe"
|
|
|
|
if (Test-Path $ffmpegExe) {
|
|
Write-Host "[OK] ffmpeg.exe found"
|
|
} else {
|
|
Write-Host "[ERROR] ffmpeg.exe missing"
|
|
}
|
|
if (Test-Path $ffprobeExe) {
|
|
Write-Host "[OK] ffprobe.exe found"
|
|
} else {
|
|
Write-Host "[ERROR] ffprobe.exe missing"
|
|
}
|
|
if (Test-Path $ffplayExe) {
|
|
Write-Host "[OK] ffplay.exe found"
|
|
} else {
|
|
Write-Host "[ERROR] ffplay.exe missing"
|
|
}
|
|
} else {
|
|
Write-Host "Error: Could not find FFmpeg directory after extraction"
|
|
exit 1
|
|
}
|
|
|
|
- name: Build FFmpeg Version (ZIP)
|
|
shell: powershell
|
|
run: |
|
|
.\venv\Scripts\Activate.ps1
|
|
$version = "$env:VERSION"
|
|
Write-Host "Building FFmpeg Version v$version ..."
|
|
|
|
# Clean previous build artifacts
|
|
if (Test-Path "build\exe.*") { Remove-Item "build\exe.*" -Recurse -Force }
|
|
if (Test-Path "build\bdist.*") { Remove-Item "build\bdist.*" -Recurse -Force }
|
|
|
|
# Create modified setup script for FFmpeg version
|
|
(Get-Content "setup_cxfreeze.py") -replace 'YTSage-v\{version\}\.exe', 'YTSage-v{version}-ffmpeg.exe' | Set-Content "setup_cxfreeze_ffmpeg.py"
|
|
|
|
# Build executable with FFmpeg using setup script
|
|
python setup_cxfreeze_ffmpeg.py build_exe --build-exe "dist\YTSage-FFmpeg"
|
|
|
|
# Remove screenshots folder to reduce build size
|
|
if (Test-Path "dist\YTSage-FFmpeg\lib\assets\branding\screenshots") {
|
|
Remove-Item "dist\YTSage-FFmpeg\lib\assets\branding\screenshots" -Recurse -Force
|
|
Write-Host "Removed screenshots folder from FFmpeg build"
|
|
}
|
|
|
|
# Copy FFmpeg binaries into dist folder post-build (more reliable than CLI include)
|
|
if ($env:FFMPEG_PATH) {
|
|
$destDir = "dist\YTSage-FFmpeg"
|
|
$ffmpegExe = Join-Path $env:FFMPEG_PATH "ffmpeg.exe"
|
|
$ffprobeExe = Join-Path $env:FFMPEG_PATH "ffprobe.exe"
|
|
$ffplayExe = Join-Path $env:FFMPEG_PATH "ffplay.exe"
|
|
|
|
foreach ($file in @($ffmpegExe, $ffprobeExe, $ffplayExe)) {
|
|
if (Test-Path $file) {
|
|
$name = Split-Path $file -Leaf
|
|
Copy-Item $file -Destination (Join-Path $destDir $name) -Force
|
|
Write-Host "Copied $name into $destDir"
|
|
} else {
|
|
Write-Host "Warning: FFmpeg binary not found: $file"
|
|
}
|
|
}
|
|
}
|
|
|
|
- name: Package and prepare release artifacts (ZIPs)
|
|
shell: powershell
|
|
run: |
|
|
$version = "${{ steps.get_version.outputs.VERSION }}"
|
|
|
|
Write-Host "Preparing ZIP artifacts for version: $version"
|
|
New-Item -ItemType Directory -Path "artifacts" -Force
|
|
|
|
# List dist directories
|
|
Write-Host "Files in dist directory:"
|
|
if (Test-Path "dist") {
|
|
Get-ChildItem -Path "dist" -Recurse | ForEach-Object { Write-Host " $($_.FullName)" }
|
|
} else {
|
|
Write-Host " No dist directory found!"
|
|
exit 1
|
|
}
|
|
|
|
# Create Standard ZIP
|
|
$standardDist = Join-Path (Get-Location) "dist\YTSage"
|
|
if (Test-Path $standardDist) {
|
|
$stdZip = "artifacts\YTSage-v$version-portable.zip"
|
|
if (Test-Path $stdZip) { Remove-Item $stdZip -Force }
|
|
Compress-Archive -Path "$standardDist\*" -DestinationPath $stdZip -CompressionLevel Optimal
|
|
Write-Host "Created: $(Resolve-Path $stdZip)"
|
|
} else {
|
|
Write-Host "Warning: Standard dist folder not found at $standardDist"
|
|
}
|
|
|
|
# Create FFmpeg ZIP
|
|
$ffmpegDist = Join-Path (Get-Location) "dist\YTSage-FFmpeg"
|
|
if (Test-Path $ffmpegDist) {
|
|
$ffZip = "artifacts\YTSage-v$version-ffmpeg-portable.zip"
|
|
if (Test-Path $ffZip) { Remove-Item $ffZip -Force }
|
|
Compress-Archive -Path "$ffmpegDist\*" -DestinationPath $ffZip -CompressionLevel Optimal
|
|
Write-Host "Created: $(Resolve-Path $ffZip)"
|
|
} else {
|
|
Write-Host "Warning: FFmpeg dist folder not found at $ffmpegDist"
|
|
}
|
|
|
|
# List all artifacts
|
|
Write-Host "Final artifacts:"
|
|
if (Test-Path "artifacts") {
|
|
$artifactFiles = Get-ChildItem artifacts
|
|
if ($artifactFiles) {
|
|
$artifactFiles | ForEach-Object { Write-Host " $($_.Name)" }
|
|
} else {
|
|
Write-Host " No artifacts created!"
|
|
}
|
|
} else {
|
|
Write-Host " Artifacts directory not found!"
|
|
}
|
|
|
|
- name: Create draft release
|
|
uses: softprops/action-gh-release@v1
|
|
with:
|
|
tag_name: ${{ github.ref_name }}
|
|
name: YTSage ${{ github.ref_name }}
|
|
draft: true
|
|
prerelease: false
|
|
body: |
|
|
# YTSage ${{ github.ref_name }}
|
|
|
|
**Release Date**: ${{ github.event.head_commit.timestamp }}
|
|
|
|
**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ github.ref_name }}
|
|
files: |
|
|
artifacts/*
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |