Merge branch 'DrewThomasson:v25' into v25

This commit is contained in:
ROBERT MCDOWELL
2025-11-11 08:31:10 -08:00
committed by GitHub
36 changed files with 380 additions and 159 deletions

View File

@@ -16,7 +16,6 @@ RUN apt-get update && \
# Install Rust compiler
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
# Set the working directory
WORKDIR /app
# Install UniDic (non-torch dependent)
RUN pip install --no-cache-dir unidic-lite unidic && \
@@ -31,74 +30,61 @@ ARG TORCH_VERSION=""
# Add parameter to control whether to skip the XTTS test
ARG SKIP_XTTS_TEST="false"
# Copy the application
WORKDIR /app
COPY . /app
# Extract torch versions from requirements.txt or set to empty strings if not found
RUN TORCH_VERSION_REQ=$(grep -E "^torch==" requirements.txt | cut -d'=' -f3 || echo "") && \
TORCHAUDIO_VERSION_REQ=$(grep -E "^torchaudio==" requirements.txt | cut -d'=' -f3 || echo "") && \
TORCHVISION_VERSION_REQ=$(grep -E "^torchvision==" requirements.txt | cut -d'=' -f3 || echo "") && \
echo "Found in requirements: torch==$TORCH_VERSION_REQ torchaudio==$TORCHAUDIO_VERSION_REQ torchvision==$TORCHVISION_VERSION_REQ"
# Install PyTorch with CUDA support if specified
# Install requirements.txt or PyTorch variants based on TORCH_VERSION
RUN if [ ! -z "$TORCH_VERSION" ]; then \
# Check if we need to use specific versions or get the latest
if [ ! -z "$TORCH_VERSION_REQ" ] && [ ! -z "$TORCHVISION_VERSION_REQ" ] && [ ! -z "$TORCHAUDIO_VERSION_REQ" ]; then \
echo "Using specific versions from requirements.txt" && \
TORCH_SPEC="torch==${TORCH_VERSION_REQ}" && \
TORCHVISION_SPEC="torchvision==${TORCHVISION_VERSION_REQ}" && \
TORCHAUDIO_SPEC="torchaudio==${TORCHAUDIO_VERSION_REQ}"; \
else \
echo "Using latest versions for the selected variant" && \
TORCH_SPEC="torch" && \
TORCHVISION_SPEC="torchvision" && \
TORCHAUDIO_SPEC="torchaudio"; \
fi && \
\
# Check if TORCH_VERSION contains "cuda" and extract version number
if echo "$TORCH_VERSION" | grep -q "cuda"; then \
CUDA_VERSION=$(echo "$TORCH_VERSION" | sed 's/cuda//g') && \
echo "Detected CUDA version: $CUDA_VERSION" && \
echo "Attempting to install PyTorch nightly for CUDA $CUDA_VERSION..." && \
#if ! pip install --no-cache-dir --pre $TORCH_SPEC $TORCHVISION_SPEC $TORCHAUDIO_SPEC --index-url https://download.pytorch.org/whl/nightly/cu${CUDA_VERSION}; then \
if ! pip install --no-cache-dir --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/cu${CUDA_VERSION}; then \
echo "❌ Nightly build for CUDA $CUDA_VERSION not available or failed" && \
echo "🔄 Trying stable release for CUDA $CUDA_VERSION..." && \
#if pip install --no-cache-dir $TORCH_SPEC $TORCHVISION_SPEC $TORCHAUDIO_SPEC --extra-index-url https://download.pytorch.org/whl/cu${CUDA_VERSION}; then \
if pip install --no-cache-dir torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu${CUDA_VERSION}; then \
echo "✅ Successfully installed stable PyTorch for CUDA $CUDA_VERSION"; \
else \
echo "❌ Both nightly and stable builds failed for CUDA $CUDA_VERSION"; \
echo "💡 This CUDA version may not be supported by PyTorch"; \
exit 1; \
fi; \
\
# Special handling for CUDA 11.8
if [ "$CUDA_VERSION" = "118" ]; then \
echo "Installing PyTorch for CUDA 11.8..." && \
pip install --no-cache-dir --upgrade -r requirements.txt && pip install pyannote-audio==3.4.0 && pip install --no-cache-dir --upgrade torch==2.7.1 torchvision==2.7.1 torchaudio==2.7.1 --index-url https://download.pytorch.org/whl/cu118; \
elif [ "$CUDA_VERSION" = "128" ]; then \
echo "Installing PyTorch for CUDA 12.8..." && \
pip install --no-cache-dir --upgrade -r requirements.txt && pip install --no-cache-dir --upgrade torch==2.7.1 torchaudio==2.7.1 --index-url https://download.pytorch.org/whl/cu128; \
else \
echo "✅ Successfully installed nightly PyTorch for CUDA $CUDA_VERSION"; \
echo "Attempting to install stable PyTorch for CUDA $CUDA_VERSION..." && \
if ! pip install --no-cache-dir --upgrade -r requirements.txt && pip install --no-cache-dir --upgrade torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu${CUDA_VERSION}; then \
echo "❌ Stable build for CUDA $CUDA_VERSION not available or failed" && \
echo "🔄 Trying nightly release for CUDA $CUDA_VERSION..." && \
if pip install --no-cache-dir --upgrade -r requirements.txt && pip install --no-cache-dir --upgrade --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/cu${CUDA_VERSION}; then \
echo "✅ Successfully installed nightly PyTorch for CUDA $CUDA_VERSION"; \
else \
echo "❌ Both stable and nightly builds failed for CUDA $CUDA_VERSION"; \
echo "💡 This CUDA version may not be supported by PyTorch"; \
exit 1; \
fi; \
else \
echo "✅ Successfully installed stable PyTorch for CUDA $CUDA_VERSION"; \
fi; \
fi; \
else \
# Handle non-CUDA cases (existing functionality)
# Handle non-CUDA cases
case "$TORCH_VERSION" in \
"rocm") \
# Using the correct syntax for ROCm PyTorch installation
pip install --no-cache-dir $TORCH_SPEC $TORCHVISION_SPEC $TORCHAUDIO_SPEC --extra-index-url https://download.pytorch.org/whl/rocm6.2 \
pip install --no-cache-dir --upgrade -r requirements.txt && pip install --no-cache-dir --upgrade torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm6.2 \
;; \
"xpu") \
# Install PyTorch with Intel XPU support through IPEX
pip install --no-cache-dir $TORCH_SPEC $TORCHVISION_SPEC $TORCHAUDIO_SPEC && \
pip install --no-cache-dir --upgrade -r requirements.txt && pip install --no-cache-dir --upgrade torch torchvision torchaudio && \
pip install --no-cache-dir intel-extension-for-pytorch --extra-index-url https://pytorch-extension.intel.com/release-whl/stable/xpu/us/ \
;; \
"cpu") \
pip install --no-cache-dir $TORCH_SPEC $TORCHVISION_SPEC $TORCHAUDIO_SPEC --extra-index-url https://download.pytorch.org/whl/cpu \
pip install --no-cache-dir --upgrade -r requirements.txt && pip install --no-cache-dir --upgrade torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu \
;; \
*) \
pip install --no-cache-dir $TORCH_VERSION \
echo "Installing custom PyTorch specification: $TORCH_VERSION" && \
pip install --no-cache-dir --upgrade -r requirements.txt && pip install --no-cache-dir --upgrade $TORCH_VERSION \
;; \
esac; \
fi && \
# Install remaining requirements, skipping torch packages that might be there
grep -v -E "^torch==|^torchvision==|^torchaudio==|^torchvision$" requirements.txt > requirements_no_torch.txt && \
pip install --no-cache-dir --upgrade -r requirements_no_torch.txt && \
rm requirements_no_torch.txt; \
fi; \
else \
# Install all requirements as specified
echo "No TORCH_VERSION specified, using packages from requirements.txt" && \
pip install --no-cache-dir --upgrade -r requirements.txt; \
fi
@@ -114,9 +100,6 @@ RUN if [ "$SKIP_XTTS_TEST" != "true" ]; then \
echo "Skipping XTTS test run as requested."; \
fi
# Copy the application
COPY . /app
# Expose the required port
EXPOSE 7860
# Start the Gradio app with the required flag
@@ -126,3 +109,12 @@ ENTRYPOINT ["python", "app.py", "--script_mode", "full_docker"]
#docker build --pull --build-arg BASE_IMAGE=athomasson2/ebook2audiobook:latest -t your-image-name .
#The --pull flag forces Docker to always try to pull the latest version of the image, even if it already exists locally.
#Without --pull, Docker will only use the local version if it exists, which might not be the latest.
# Example build commands:
# For CUDA 11.8: docker build --build-arg TORCH_VERSION=cuda118 -t your-image-name .
# For CUDA 12.8: docker build --build-arg TORCH_VERSION=cuda128 -t your-image-name .
# For CUDA 12.1: docker build --build-arg TORCH_VERSION=cuda121 -t your-image-name .
# For ROCm: docker build --build-arg TORCH_VERSION=rocm -t your-image-name .
# For CPU: docker build --build-arg TORCH_VERSION=cpu -t your-image-name .
# For XPU: docker build --build-arg TORCH_VERSION=xpu -t your-image-name .
# Default (no TORCH_VERSION): docker build -t your-image-name .

143
README.md
View File

@@ -83,18 +83,18 @@ https://github.com/user-attachments/assets/81c4baad-117e-4db5-ac86-efc2b7fea921
- [Basic Headless Usage](#basic--usage)
- [Headless Custom XTTS Model Usage](#example-of-custom-model-zip-upload)
- [Help command output](#help-command-output)
- [Run Remotely](#run-remotely)
- [Run Remotely](#run-remotely)
- [Docker](#docker-compose)
- [Docker Compose (Recommended)](#docker-compose)
- [Docker Compose Headless](#compose-headless)
- [Compose Build Arguments](#compose-build-arguments)
- [Compose container file locations](#compose-container-file-locations)
- [Common Docker issues](#common-docker-issues)
- [Docker Build (Manual)](https://github.com/DrewThomasson/ebook2audiobook/wiki/Manual-Docker-Guide)
- [Fine Tuned TTS models](#fine-tuned-tts-models)
- [Collection of Fine-Tuned TTS Models](#fine-tuned-tts-collection)
- [Train XTTSv2](#fine-tune-your-own-xttsv2-model)
- [Docker](#docker-gpu-options)
- [GPU options](#docker-gpu-options)
- [Docker Run](#running-the-pre-built-docker-container)
- [Docker Build](#building-the-docker-container)
- [Docker Compose](#docker-compose)
- [Docker headless guide](#docker-headless-guide)
- [Docker container file locations](#docker-container-file-locations)
- [Common Docker issues](#common-docker-issues)
- [Supported eBook Formats](#supported-ebook-formats)
- [Output Formats](#output-formats)
- [Updating to Latest Version](#updating-to-latest-version)
@@ -169,9 +169,9 @@ cd ebook2audiobook
1. **Open the Web App**: Click the URL provided in the terminal to access the web app and convert eBooks. `http://localhost:7860/`
2. **For Public Link**:
`python app.py --share` (all OS)
`./ebook2audiobook.sh --share` (Linux/MacOS)
`ebook2audiobook.cmd --share` (Windows)
`python app.py --share` (all OS)
> [!IMPORTANT]
**If the script is stopped and run again, you need to refresh your gradio GUI interface<br>
@@ -333,84 +333,11 @@ NOTE: in gradio/gui mode, to cancel a running conversion, just click on the [X]
TIP: if it needs some more pauses, just add '###' or '[pause]' between the words you wish more pause. one [pause] equals to 1.4 seconds
#### Docker GPU Options
Available pre-build tags: `latest` (CUDA 11.8)
#### Edit: IF GPU isn't detected then you'll have to build the image -> [Building the Docker Container](#building-the-docker-container)
#### Running the pre-built Docker Container
-Run with CPU only
```powershell
docker run --pull always --rm -p 7860:7860 athomasson2/ebook2audiobook
```
-Run with GPU Speedup (NVIDIA compatible only)
```powershell
docker run --pull always --rm --gpus all -p 7860:7860 athomasson2/ebook2audiobook
```
This command will start the Gradio interface on port 7860.(localhost:7860)
- For more options add the parameter `--help`
#### Building the Docker Container
- You can build the docker image with the command:
```powershell
docker build -t athomasson2/ebook2audiobook .
```
#### Avalible Docker Build Arguments
`--build-arg TORCH_VERSION=cuda118` Available tags: [cuda121, cuda118, cuda128, rocm, xpu, cpu]
All CUDA version numbers should work, Ex: CUDA 11.6-> cuda116
`--build-arg SKIP_XTTS_TEST=true` (Saves space by not baking XTTSv2 model into docker image)
## Docker container file locations
All ebook2audiobooks will have the base dir of `/app/`
For example:
`tmp` = `/app/tmp`
`audiobooks` = `/app/audiobooks`
## Docker headless guide
> [!IMPORTANT]
**For simpler headless setup use the [Compose](#compose-headless).** <br>
- Before you do run this you need to create a dir named "input-folder" in your current dir
which will be linked, This is where you can put your input files for the docker image to see
```bash
mkdir input-folder && mkdir Audiobooks
```
- In the command below swap out **YOUR_INPUT_FILE.TXT** with the name of your input file
```bash
docker run --pull always --rm \
-v $(pwd)/input-folder:/app/input_folder \
-v $(pwd)/audiobooks:/app/audiobooks \
athomasson2/ebook2audiobook \
--headless --ebook /input_folder/YOUR_EBOOK_FILE
```
- The output Audiobooks will be found in the Audiobook folder which will also be located
in your local dir you ran this docker command in
## To get the help command for the other parameters this program has you can run this
```bash
docker run --pull always --rm athomasson2/ebook2audiobook --help
```
That will output this
[Help command output](#help-command-output)
### Docker Compose
This project uses Docker Compose to run locally. You can enable or disable GPU support
by setting either `*gpu-enabled` or `*gpu-disabled` in `docker-compose.yml`
For pre-built image enable `#image: docker.io/athomasson2/ebook2audiobook:latest` in `docker-compose.yml`
#### Steps to Run
@@ -421,46 +348,48 @@ by setting either `*gpu-enabled` or `*gpu-disabled` in `docker-compose.yml`
```
2. **Set GPU Support (disabled by default)**
To enable GPU support, modify `docker-compose.yml` and change `*gpu-disabled` to `*gpu-enabled`
3. **Start the service:**
4. **Start the service:**
```bash
# Docker
docker-compose up -d # To rebuild add --build
docker-compose up -d # To rebuild add --build
# To stop -> docker-compose down
# Podman
podman compose -f podman-compose.yml up -d # To rebuild add --build
# To stop -> podman compose -f podman-compose.yml down
```
4. **Access the service:**
5. **Access the service:**
The service will be available at http://localhost:7860.
### Compose Build Arguments
```bash
SKIP_XTTS_TEST: "true" # (Saves space by not baking xtts model into docker image)
TORCH_VERSION: cuda118 # Available tags: [cuda121, cuda118, cuda128, rocm, xpu, cpu] # All CUDA version numbers should work, Ex: CUDA 11.6-> cuda116
```
### Compose Headless
[Headless Wiki for more info](https://github.com/DrewThomasson/ebook2audiobook/wiki/Docker-Compose-Headless-guide)
```bash
A headless example is already contained within the `docker-compose.yml` file.
The `docker-compose.yml` file will act as the base dir for any headless commands added.
```
### Compose container file locations
```bash
By Default: All compose containers share the contents your local `ebook2audiobook` folder
```
## Common Docker Issues
### Common Docker Issues
- My NVIDIA GPU isnt being detected?? -> [GPU ISSUES Wiki Page](https://github.com/DrewThomasson/ebook2audiobook/wiki/GPU-ISSUES)
- `python: can't open file '/home/user/app/app.py': [Errno 2] No such file or directory` (Just remove all post arguments as I replaced the `CMD` with `ENTRYPOINT` in the [Dockerfile](Dockerfile))
- Example: `docker run --pull always athomasson2/ebook2audiobook app.py --script_mode full_docker` - > corrected - > `docker run --pull always athomasson2/ebook2audiobook`
- Arguments can be easily added like this now `docker run --pull always athomasson2/ebook2audiobook --share`
- Docker gets stuck downloading Fine-Tuned models.
(This does not happen for every computer but some appear to run into this issue)
Disabling the progress bar appears to fix the issue,
as discussed [here in #191](https://github.com/DrewThomasson/ebook2audiobook/issues/191)
Example of adding this fix in the `docker run` command
```Dockerfile
docker run --pull always --rm --gpus all -e HF_HUB_DISABLE_PROGRESS_BARS=1 -e HF_HUB_ENABLE_HF_TRANSFER=0 \
-p 7860:7860 athomasson2/ebook2audiobook
```
## Fine Tuned TTS models
#### Fine Tune your own XTTSv2 model

View File

@@ -405,6 +405,115 @@ else
return 0
}
function create_macos_app_bundle {
local APP_NAME="ebook2audiobook"
local APP_BUNDLE="$HOME/Applications/$APP_NAME.app"
local CONTENTS="$APP_BUNDLE/Contents"
local MACOS="$CONTENTS/MacOS"
local RESOURCES="$CONTENTS/Resources"
local ICON_PATH="$SCRIPT_DIR/icons/mac/appIcon.icns"
echo "🚀 Creating $APP_NAME.app bundle..."
mkdir -p "$MACOS" "$RESOURCES"
# Create the executable script inside the bundle
cat > "$MACOS/$APP_NAME" << EOF
#!/bin/bash
# Create a temporary script file to run in Terminal
TEMP_SCRIPT=\$(mktemp)
cat > "\$TEMP_SCRIPT" << 'SCRIPT'
#!/bin/bash
cd "$SCRIPT_DIR"
conda deactivate
bash ebook2audiobook.sh
# Wait 10 seconds for the server to start
sleep 10
# Open the browser
open http://localhost:7860/
SCRIPT
chmod +x "\$TEMP_SCRIPT"
# Open Terminal and run the script
open -a Terminal "\$TEMP_SCRIPT"
# Clean up the temp script after 60 seconds
sleep 60
rm "\$TEMP_SCRIPT"
EOF
chmod +x "$MACOS/$APP_NAME"
# Copy the icon to the bundle
if [ -f "$ICON_PATH" ]; then
cp "$ICON_PATH" "$RESOURCES/AppIcon.icns"
echo "✓ Icon copied to bundle"
else
echo "⚠️ Warning: Icon not found at $ICON_PATH"
fi
# Create the Info.plist file (required for macOS app bundles)
cat > "$CONTENTS/Info.plist" << 'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>ebook2audiobook</string>
<key>CFBundleIdentifier</key>
<string>com.local.ebook2audiobook</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>ebook2audiobook</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>10.9</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
</dict>
</plist>
PLIST
echo "✓ Info.plist created"
# Update macOS cache to recognize the new app
touch "$APP_BUNDLE"
echo ""
echo "✅ Application bundle created successfully!"
echo "📍 Location: $APP_BUNDLE"
echo ""
}
function create_linux_app_launcher {
# Linux desktop entry creation goes here
return 0
}
function create_app_bundle {
if [[ "$OSTYPE" = "darwin"* ]]; then
create_macos_app_bundle
elif [[ "$OSTYPE" = "linux"* ]]; then
create_linux_app_launcher
fi
}
if [ "$SCRIPT_MODE" = "$FULL_DOCKER" ]; then
python app.py --script_mode "$SCRIPT_MODE" "${ARGS[@]}"
conda deactivate
@@ -421,6 +530,7 @@ else
conda init > /dev/null 2>&1
source $CONDA_ENV
conda activate "$SCRIPT_DIR/$PYTHON_ENV"
create_app_bundle
python app.py --script_mode "$SCRIPT_MODE" "${ARGS[@]}"
conda deactivate
conda deactivate
@@ -431,4 +541,4 @@ else
fi
fi
exit 0
exit 0

View File

@@ -329,6 +329,22 @@ models = {
"files": default_engine_settings[TTS_ENGINES['XTTSv2']]['files'],
"samplerate": default_engine_settings[TTS_ENGINES['XTTSv2']]['samplerate']
},
"PeterGriffinFamilyGuy": {
"lang": "eng",
"repo": "drewThomasson/fineTunedTTSModels",
"sub": "xtts-v2/eng/PeterGriffinFamilyGuy/",
"voice": os.path.join(voices_dir, 'eng', 'adult', 'male', 'PeterGriffinFamilyGuy.wav'),
"files": default_engine_settings[TTS_ENGINES['XTTSv2']]['files'],
"samplerate": default_engine_settings[TTS_ENGINES['XTTSv2']]['samplerate']
},
"RafeBeckley": {
"lang": "eng",
"repo": "drewThomasson/fineTunedTTSModels",
"sub": "xtts-v2/eng/RafeBeckley/",
"voice": os.path.join(voices_dir, 'eng', 'adult', 'male', 'RafeBeckley.wav'),
"files": default_engine_settings[TTS_ENGINES['XTTSv2']]['files'],
"samplerate": default_engine_settings[TTS_ENGINES['XTTSv2']]['samplerate']
},
"RainyDayHeadSpace": {
"lang": "eng",
"repo": "drewThomasson/fineTunedTTSModels",
@@ -496,3 +512,4 @@ models = {
}
}

BIN
tools/icons/appLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 KiB

View File

@@ -0,0 +1,173 @@
#!/usr/bin/env python3
"""
Multi-platform icon generator
Converts appLogo.png into platform-specific formats and sizes
Requires: Pillow (PIL), cairosvg (optional for SVG)
Installation:
pip install Pillow cairosvg
"""
import os
import sys
from PIL import Image
# Icon sizes for each platform
ICON_SIZES = {
'windows': [16, 24, 32, 48, 256],
'mac': [16, 32, 64, 128, 256, 512, 1024],
'linux': [16, 24, 32, 48, 64, 128, 256]
}
def create_directories():
"""Create output directories for each platform"""
for platform in ICON_SIZES.keys():
os.makedirs(f'icons/{platform}', exist_ok=True)
print("✓ Directories created")
def resize_image(source_path, output_dir, sizes):
"""Resize image to multiple sizes"""
try:
img = Image.open(source_path)
# Convert to RGBA to ensure transparency support
img = img.convert('RGBA')
for size in sizes:
resized = img.resize((size, size), Image.Resampling.LANCZOS)
output_path = f'{output_dir}/icon-{size}.png'
resized.save(output_path, 'PNG')
print(f" ✓ Generated {size}x{size} icon")
return True
except Exception as e:
print(f"✗ Error resizing image: {e}")
return False
def create_windows_ico(output_dir):
"""Create Windows ICO file from PNGs"""
try:
sizes = ICON_SIZES['windows']
images = []
for size in sizes:
img_path = f'{output_dir}/icon-{size}.png'
images.append(Image.open(img_path))
# Save as ICO with multiple sizes
images[0].save(
f'{output_dir}/appIcon.ico',
format='ICO',
sizes=[(size, size) for size in sizes]
)
print("✓ Windows ICO file created: icons/windows/appIcon.ico")
return True
except Exception as e:
print(f"✗ Error creating ICO: {e}")
return False
def create_mac_icns(output_dir):
"""Create macOS ICNS file from PNGs (requires imagemagick or online conversion)"""
try:
import subprocess
sizes = ICON_SIZES['mac']
# Create iconset directory
iconset_dir = f'{output_dir}/appIcon.iconset'
os.makedirs(iconset_dir, exist_ok=True)
for size in sizes:
img_path = f'{output_dir}/icon-{size}.png'
# macOS uses specific naming conventions
scale = 2 if size > 256 else 1
icon_name = f'icon_{size // scale}x{size // scale}'
if scale == 2:
icon_name += '@2x'
output_path = f'{iconset_dir}/{icon_name}.png'
os.system(f'cp {img_path} {output_path}')
# Try to create ICNS using iconutil (macOS only) or convert
try:
subprocess.run(['iconutil', '-c', 'icns', '-o',
f'{output_dir}/appIcon.icns', iconset_dir],
check=True, capture_output=True)
print("✓ macOS ICNS file created: icons/mac/appIcon.icns")
except (subprocess.CalledProcessError, FileNotFoundError):
print("⚠ Note: iconutil not found. ICNS not created.")
print(" On macOS, run: iconutil -c icns -o icons/mac/appIcon.icns icons/mac/appIcon.iconset")
return False
return True
except Exception as e:
print(f"✗ Error creating ICNS: {e}")
return False
def create_svg_copy(source_path, output_dir):
"""Create SVG copy for Linux (optional, requires vector source)"""
try:
import shutil
svg_path = source_path.replace('.png', '.svg')
if os.path.exists(svg_path):
shutil.copy(svg_path, f'{output_dir}/appIcon.svg')
print(f"✓ SVG icon copied: icons/linux/appIcon.svg")
return True
else:
print("⚠ No SVG source found (optional for Linux)")
return True
except Exception as e:
print(f"✗ Error copying SVG: {e}")
return False
def main():
"""Main execution"""
print("🎨 Multi-Platform Icon Generator\n")
# Find source image
source_image = 'appLogo.png'
if not os.path.exists(source_image):
print(f"✗ Error: {source_image} not found in current directory")
sys.exit(1)
print(f"Source: {source_image}\n")
# Create directories
create_directories()
print()
# Generate icons for each platform
for platform, sizes in ICON_SIZES.items():
print(f"Generating {platform.upper()} icons...")
output_dir = f'icons/{platform}'
if not resize_image(source_image, output_dir, sizes):
sys.exit(1)
print()
# Create platform-specific formats
print("Creating platform-specific formats...\n")
if not create_windows_ico('icons/windows'):
print("⚠ Continuing despite ICO creation issue\n")
if not create_mac_icns('icons/mac'):
print("⚠ Continuing despite ICNS creation issue\n")
if not create_svg_copy(source_image, 'icons/linux'):
print("⚠ Continuing despite SVG copy issue\n")
print("✅ Icon generation complete!")
print("\nOutput structure:")
print(" icons/")
print(" ├── windows/")
print(" │ ├── appIcon.ico")
print(" │ └── icon-*.png")
print(" ├── mac/")
print(" │ ├── appIcon.icns (if created)")
print(" │ └── icon-*.png")
print(" └── linux/")
print(" ├── appIcon.svg (if available)")
print(" └── icon-*.png")
if __name__ == '__main__':
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
tools/icons/mac/icon-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
tools/icons/mac/icon-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
tools/icons/mac/icon-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Binary file not shown.