feat: standalone extractor - professional polish and hardcoded settings

Major updates to standalone_icon_extractor.py:

Settings:
- Hardcoded to 320x320 canvas size (removed upscale options)
- Output folder uses Path.home() for any Windows username
- Added explanation about output location (Documents/Entropia Universe/Icons)

UI Changes:
- Removed game controller emoji from title
- Button changed to "Start Extracting Icons" (no rocket emoji)
- Removed all emojis from UI for professional look

Attribution & Legal:
- Footer: "Developed by ImpulsiveFPS"
- Discord contact: impulsivefps
- Disclaimer: Not affiliated with Entropia Universe or Mindark PE AB
- Trademark notice for Entropia Universe

Features:
- Added EntropiaNexus.com link and button
- Added explanation that items must be seen in-game to be cached
- Added "Important Information" box explaining cache requirements
- Icon support (looks for assets/icon.ico or icon.ico)

Technical:
- Uses QSettings under ImpulsiveFPS organization
- Added set_app_icon() function for custom icon support
This commit is contained in:
LemonNexus 2026-02-11 18:13:05 +00:00
parent 8b97dbc58c
commit eba76cf59c
1 changed files with 223 additions and 228 deletions

View File

@ -1,45 +1,57 @@
"""
Entropia Universe Icon Extractor
A standalone tool for extracting and converting game icons from cache.
A standalone tool for extracting game icons from cache.
Description: Standalone GUI tool to extract TGA icons from Entropia Universe cache,
convert them to PNG, and optionally upscale them for better quality.
Description: Extracts item icons from Entropia Universe game cache and converts
them to PNG format for use with EntropiaNexus.com wiki submissions.
Important: Items must be seen/rendered in-game before they appear in the cache.
Usage:
python standalone_icon_extractor.py
Author: LemonNexus
Output Location:
Icons are saved to your Documents/Entropia Universe/Icons/ folder
(same location where chat.log is normally stored)
Developer: ImpulsiveFPS
Discord: impulsivefps
Website: https://EntropiaNexus.com
Disclaimer:
This tool is not affiliated with, endorsed by, or connected to
Entropia Universe or Mindark PE AB in any way.
Entropia Universe is a registered trademark of Mindark PE AB.
"""
import sys
import logging
from pathlib import Path
from typing import Optional, List, Tuple
from dataclasses import dataclass
from enum import Enum
import ctypes
try:
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QPushButton, QComboBox, QListWidget, QListWidgetItem,
QFileDialog, QProgressBar, QGroupBox, QMessageBox, QCheckBox,
QSpinBox, QSplitter
QSplitter, QTextEdit
)
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QSettings
from PyQt6.QtGui import QIcon, QPixmap
from PyQt6.QtGui import QIcon, QPixmap, QFont
PYQT_AVAILABLE = True
except ImportError:
PYQT_AVAILABLE = False
print("PyQt6 not available. Install with: pip install PyQt6")
sys.exit(1)
try:
from PIL import Image, ImageFilter
PIL_AVAILABLE = True
except ImportError:
PIL_AVAILABLE = False
print("PIL not available. Install with: pip install Pillow")
import numpy as np
print("Pillow not available. Install with: pip install Pillow")
sys.exit(1)
# Setup logging
logging.basicConfig(
@ -49,6 +61,14 @@ logging.basicConfig(
logger = logging.getLogger(__name__)
# Application metadata
APP_NAME = "Entropia Universe Icon Extractor"
APP_VERSION = "1.0.0"
DEVELOPER = "ImpulsiveFPS"
DISCORD = "impulsivefps"
WEBSITE = "https://EntropiaNexus.com"
class TGAHeader:
"""TGA file header structure."""
def __init__(self, data: bytes):
@ -70,26 +90,28 @@ class TGAHeader:
class TGAConverter:
"""Converter for TGA files to PNG with canvas and upscaling options."""
"""Converter for TGA files to PNG with 320x320 canvas."""
CANVAS_SIZE = (320, 320) # Hardcoded to 320x320
def __init__(self, output_dir: Optional[Path] = None):
self.output_dir = output_dir or Path.home() / "Documents" / "Entropia Universe" / "Icons"
# Default to user's Documents/Entropia Universe/Icons/
# This works on any Windows username
if output_dir is None:
self.output_dir = Path.home() / "Documents" / "Entropia Universe" / "Icons"
else:
self.output_dir = output_dir
self.output_dir.mkdir(parents=True, exist_ok=True)
self._cache_path: Optional[Path] = None
def find_cache_folder(self) -> Optional[Path]:
"""Find the Entropia Universe icon cache folder."""
possible_paths = [
Path("C:") / "ProgramData" / "Entropia Universe" / "public_users_data" / "cache" / "icon",
Path.home() / "Documents" / "Entropia Universe" / "cache" / "icons",
]
# Hardcoded path - works on any system with EU installed
cache_path = Path("C:/ProgramData/Entropia Universe/public_users_data/cache/icon")
for path in possible_paths:
if path.exists():
# Check if this folder or any subfolder has .tga files
if list(path.rglob("*.tga")):
self._cache_path = path
return path
if cache_path.exists():
self._cache_path = cache_path
return cache_path
return None
@ -105,25 +127,25 @@ class TGAConverter:
logger.error(f"Error reading TGA header: {e}")
return None
def convert_tga_to_png(
self,
tga_path: Path,
output_name: Optional[str] = None,
canvas_size: Optional[Tuple[int, int]] = None,
upscale: bool = False,
upscale_method: str = 'nearest'
) -> Optional[Path]:
"""Convert a TGA file to PNG."""
def convert_tga_to_png(self, tga_path: Path, output_name: Optional[str] = None) -> Optional[Path]:
"""
Convert a TGA file to PNG with 320x320 canvas.
Args:
tga_path: Path to TGA file
output_name: Optional custom output name
Returns:
Path to output PNG file or None if failed
"""
try:
# Try PIL first
# Load TGA
image = Image.open(tga_path)
if image.mode != 'RGBA':
image = image.convert('RGBA')
# Apply canvas sizing if requested
if canvas_size:
do_upscale = upscale and upscale_method != 'none'
image = self._apply_canvas(image, canvas_size, do_upscale, upscale_method)
# Apply 320x320 canvas (centered, no upscaling)
image = self._apply_canvas(image)
# Save
if output_name is None:
@ -138,45 +160,18 @@ class TGAConverter:
logger.error(f"Conversion failed: {e}")
return None
def _apply_canvas(
self,
image: Image.Image,
canvas_size: Tuple[int, int],
upscale: bool = False,
upscale_method: str = 'nearest'
) -> Image.Image:
"""Place image centered on a canvas."""
canvas_w, canvas_h = canvas_size
def _apply_canvas(self, image: Image.Image) -> Image.Image:
"""
Place image centered on a 320x320 canvas.
No upscaling - original size centered on canvas.
"""
canvas_w, canvas_h = self.CANVAS_SIZE
img_w, img_h = image.size
# Create transparent canvas
canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))
canvas = Image.new('RGBA', self.CANVAS_SIZE, (0, 0, 0, 0))
# Calculate scaling
if upscale:
max_size = int(min(canvas_w, canvas_h) * 0.85)
scale = min(max_size / img_w, max_size / img_h)
if scale > 1:
new_w = int(img_w * scale)
new_h = int(img_h * scale)
if upscale_method == 'nearest':
image = image.resize((new_w, new_h), Image.Resampling.NEAREST)
elif upscale_method == 'hq4x':
int_scale = max(2, int(scale))
temp_w = img_w * int_scale
temp_h = img_h * int_scale
image = image.resize((temp_w, temp_h), Image.Resampling.NEAREST)
image = image.resize((new_w, new_h), Image.Resampling.LANCZOS)
image = image.filter(ImageFilter.EDGE_ENHANCE_MORE)
else: # lanczos
image = image.resize((new_w, new_h), Image.Resampling.LANCZOS)
image = image.filter(ImageFilter.UnsharpMask(radius=2, percent=150, threshold=3))
img_w, img_h = new_w, new_h
# Center on canvas
# Center on canvas (no scaling)
x = (canvas_w - img_w) // 2
y = (canvas_h - img_h) // 2
@ -191,18 +186,10 @@ class ConversionWorker(QThread):
finished = pyqtSignal(int, int) # success, total
error = pyqtSignal(str)
def __init__(
self,
files: List[Path],
converter: TGAConverter,
canvas_size: Optional[Tuple[int, int]],
upscale_method: str
):
def __init__(self, files: List[Path], converter: TGAConverter):
super().__init__()
self.files = files
self.converter = converter
self.canvas_size = canvas_size
self.upscale_method = upscale_method
self._running = True
def run(self):
@ -217,12 +204,7 @@ class ConversionWorker(QThread):
self.progress.emit(f"[{i+1}/{total}] {filepath.name}")
output = self.converter.convert_tga_to_png(
filepath,
canvas_size=self.canvas_size,
upscale=True,
upscale_method=self.upscale_method
)
output = self.converter.convert_tga_to_png(filepath)
if output:
success += 1
@ -242,18 +224,17 @@ class IconExtractorWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("🎮 Entropia Universe Icon Extractor")
self.setMinimumSize(900, 700)
self.setWindowTitle(APP_NAME)
self.setMinimumSize(950, 750)
self.converter = TGAConverter()
self.worker: Optional[ConversionWorker] = None
self.found_files: List[Path] = []
self.current_subfolder: Optional[Path] = None
# Hardcoded base cache path
self.base_cache_path = Path("C:/ProgramData/Entropia Universe/public_users_data/cache/icon")
self.settings = QSettings("Lemontropia", "IconExtractor")
self.settings = QSettings("ImpulsiveFPS", "EUIconExtractor")
self._setup_ui()
self._load_settings()
@ -268,18 +249,44 @@ class IconExtractorWindow(QMainWindow):
layout.setSpacing(12)
# Header
header = QLabel("🎮 Entropia Universe Icon Extractor")
header.setStyleSheet("font-size: 20px; font-weight: bold; color: #4caf50;")
header = QLabel(APP_NAME)
header.setStyleSheet("font-size: 22px; font-weight: bold; color: #4caf50;")
layout.addWidget(header)
# Description
desc = QLabel(
"Extract and convert item icons from Entropia Universe game cache.\n"
"Icons are saved as PNG files with transparent backgrounds."
"Extract item icons from Entropia Universe game cache and convert them to PNG format.\n"
"These icons can be submitted to EntropiaNexus.com to help complete the item database."
)
desc.setStyleSheet("color: #888; padding: 5px;")
desc.setStyleSheet("color: #aaaaaa; padding: 5px;")
desc.setWordWrap(True)
layout.addWidget(desc)
# Important notice
notice_group = QGroupBox("Important Information")
notice_layout = QVBoxLayout(notice_group)
notice_text = QTextEdit()
notice_text.setReadOnly(True)
notice_text.setMaximumHeight(120)
notice_text.setStyleSheet("""
QTextEdit {
background-color: #2a2520;
color: #ffcc80;
border: 1px solid #5d4037;
border-radius: 3px;
font-size: 12px;
}
""")
notice_text.setText(
"REQUIREMENT: Items must be seen/rendered in-game before they appear in the cache!\n"
"If an item icon is missing, you need to view the item in your inventory or see it dropped as loot first.\n\n"
f"Output Location: Icons are saved to your Documents folder ({self.converter.output_dir})\n"
"in the same location where Entropia Universe normally stores your chat.log file."
)
notice_layout.addWidget(notice_text)
layout.addWidget(notice_group)
# Main splitter
splitter = QSplitter(Qt.Orientation.Horizontal)
@ -289,15 +296,15 @@ class IconExtractorWindow(QMainWindow):
left_layout.setContentsMargins(0, 0, 0, 0)
# Cache folder
cache_group = QGroupBox("📁 Cache Folder")
cache_group = QGroupBox("Cache Source")
cache_layout = QVBoxLayout(cache_group)
# Base path (hardcoded)
base_label = QLabel("Base Path:")
base_label = QLabel("Game Cache Location:")
cache_layout.addWidget(base_label)
self.cache_label = QLabel(str(self.base_cache_path))
self.cache_label.setStyleSheet("font-family: Consolas; color: #888; padding: 5px; background: #1a1a1a; border-radius: 3px;")
self.cache_label.setStyleSheet("font-family: Consolas; font-size: 11px; color: #888; padding: 5px; background: #1a1a1a; border-radius: 3px;")
cache_layout.addWidget(self.cache_label)
# Subfolder selector
@ -308,7 +315,7 @@ class IconExtractorWindow(QMainWindow):
self.subfolder_combo.currentIndexChanged.connect(self._on_subfolder_changed)
subfolder_layout.addWidget(self.subfolder_combo)
refresh_btn = QPushButton("🔄 Refresh")
refresh_btn = QPushButton("Refresh")
refresh_btn.clicked.connect(self._detect_subfolders)
subfolder_layout.addWidget(refresh_btn)
@ -316,81 +323,68 @@ class IconExtractorWindow(QMainWindow):
cache_layout.addLayout(subfolder_layout)
# All subfolders checkbox
self.all_subfolders_check = QCheckBox("Include ALL subfolders (merge everything)")
self.all_subfolders_check.setToolTip("If checked, will find TGA files from ALL version subfolders")
self.all_subfolders_check = QCheckBox("Include ALL version folders")
self.all_subfolders_check.setToolTip("Merge icons from all game versions")
self.all_subfolders_check.stateChanged.connect(self._on_all_subfolders_changed)
cache_layout.addWidget(self.all_subfolders_check)
cache_btn_layout = QHBoxLayout()
scan_btn = QPushButton("🔍 Auto-Detect")
scan_btn.clicked.connect(self._auto_scan)
cache_btn_layout.addWidget(scan_btn)
browse_btn = QPushButton("Browse...")
browse_btn.clicked.connect(self._browse_cache)
cache_btn_layout.addWidget(browse_btn)
cache_btn_layout.addStretch()
cache_layout.addLayout(cache_btn_layout)
left_layout.addWidget(cache_group)
# Output folder
output_group = QGroupBox("💾 Output Folder")
output_group = QGroupBox("Output Location")
output_layout = QVBoxLayout(output_group)
output_info = QLabel(
"Icons will be saved to your Documents folder:\n"
"(Same location as chat.log)"
)
output_info.setStyleSheet("color: #888; font-size: 11px;")
output_layout.addWidget(output_info)
self.output_label = QLabel(str(self.converter.output_dir))
self.output_label.setStyleSheet("font-family: Consolas; color: #888; padding: 5px; background: #1a1a1a; border-radius: 3px;")
self.output_label.setStyleSheet("font-family: Consolas; font-size: 11px; color: #888; padding: 5px; background: #1a1a1a; border-radius: 3px;")
self.output_label.setWordWrap(True)
output_layout.addWidget(self.output_label)
output_btn = QPushButton("Change Output Folder...")
output_btn.clicked.connect(self._browse_output)
output_layout.addWidget(output_btn)
change_btn = QPushButton("Change Output Folder...")
change_btn.clicked.connect(self._browse_output)
output_layout.addWidget(change_btn)
left_layout.addWidget(output_group)
# Settings
settings_group = QGroupBox("⚙️ Conversion Settings")
# Settings (simplified - just 320x320)
settings_group = QGroupBox("Export Settings")
settings_layout = QVBoxLayout(settings_group)
# Canvas size
size_layout = QHBoxLayout()
size_layout.addWidget(QLabel("Canvas Size:"))
self.size_combo = QComboBox()
self.size_combo.addItem("Original (no canvas)", None)
self.size_combo.addItem("64x64", (64, 64))
self.size_combo.addItem("128x128", (128, 128))
self.size_combo.addItem("256x256", (256, 256))
self.size_combo.addItem("280x280 (Forum)", (280, 280))
self.size_combo.addItem("320x320", (320, 320))
self.size_combo.addItem("512x512", (512, 512))
self.size_combo.setCurrentIndex(5) # Default 320x320
size_layout.addWidget(self.size_combo)
size_layout.addStretch()
settings_layout.addLayout(size_layout)
# Upscale method
method_layout = QHBoxLayout()
method_layout.addWidget(QLabel("Upscale:"))
self.method_combo = QComboBox()
self.method_combo.addItem("❌ No Upscaling", "none")
self.method_combo.addItem("Sharp Pixels (NEAREST)", "nearest")
self.method_combo.addItem("Smooth (HQ4x-style)", "hq4x")
self.method_combo.addItem("Photorealistic (LANCZOS)", "lanczos")
self.method_combo.setToolTip(
"None: Keep original size\n"
"NEAREST: Sharp edges (pixel art)\n"
"HQ4x: Smooth (game icons)\n"
"LANCZOS: Very smooth (photos)"
settings_info = QLabel(
"Export Format: PNG with transparent background\n"
"Canvas Size: 320x320 pixels (centered)\n"
"Upscaling: None (original icon size)"
)
method_layout.addWidget(self.method_combo)
method_layout.addStretch()
settings_layout.addLayout(method_layout)
settings_info.setStyleSheet("color: #888; font-size: 11px;")
settings_layout.addWidget(settings_info)
left_layout.addWidget(settings_group)
# Nexus link
nexus_group = QGroupBox("EntropiaNexus.com")
nexus_layout = QVBoxLayout(nexus_group)
nexus_info = QLabel(
"Submit extracted icons to help complete the\n"
"Entropia Universe item database and wiki!"
)
nexus_info.setStyleSheet("color: #4caf50;")
nexus_layout.addWidget(nexus_info)
nexus_btn = QPushButton("Open EntropiaNexus.com")
nexus_btn.clicked.connect(lambda: self._open_url(WEBSITE))
nexus_layout.addWidget(nexus_btn)
left_layout.addWidget(nexus_group)
# Convert button
self.convert_btn = QPushButton("🚀 Convert All Icons")
self.convert_btn = QPushButton("Start Extracting Icons")
self.convert_btn.setMinimumHeight(60)
self.convert_btn.setStyleSheet("""
QPushButton {
@ -423,9 +417,13 @@ class IconExtractorWindow(QMainWindow):
right_layout = QVBoxLayout(right_panel)
right_layout.setContentsMargins(0, 0, 0, 0)
files_group = QGroupBox("📄 Found Icons")
files_group = QGroupBox("Available Icons")
files_layout = QVBoxLayout(files_group)
files_info = QLabel("Select icons to extract (or leave unselected to extract all)")
files_info.setStyleSheet("color: #888; font-size: 11px;")
files_layout.addWidget(files_info)
self.files_count_label = QLabel("No files found")
files_layout.addWidget(self.files_count_label)
@ -446,7 +444,7 @@ class IconExtractorWindow(QMainWindow):
sel_layout.addStretch()
open_folder_btn = QPushButton("📂 Open Output Folder")
open_folder_btn = QPushButton("Open Output Folder")
open_folder_btn.clicked.connect(self._open_output_folder)
sel_layout.addWidget(open_folder_btn)
@ -454,16 +452,26 @@ class IconExtractorWindow(QMainWindow):
right_layout.addWidget(files_group)
splitter.addWidget(right_panel)
splitter.setSizes([350, 550])
splitter.setSizes([380, 520])
layout.addWidget(splitter, 1)
# Footer
footer = QLabel("Entropia Universe Icon Extractor | Standalone Tool")
footer.setStyleSheet("color: #555; font-size: 11px; padding: 5px;")
footer = QLabel(
f"Developed by {DEVELOPER} | Discord: {DISCORD} | {WEBSITE}\n"
"Not affiliated with or endorsed by Entropia Universe or Mindark PE AB."
" Entropia Universe is a trademark of Mindark PE AB."
)
footer.setStyleSheet("color: #555; font-size: 10px; padding: 5px;")
footer.setAlignment(Qt.AlignmentFlag.AlignCenter)
footer.setWordWrap(True)
layout.addWidget(footer)
def _open_url(self, url: str):
"""Open URL in default browser."""
import webbrowser
webbrowser.open(url)
def _load_settings(self):
"""Load saved settings."""
# Output folder
@ -471,27 +479,17 @@ class IconExtractorWindow(QMainWindow):
self.converter.output_dir = Path(saved_output)
self.output_label.setText(saved_output)
# Canvas size
size_index = int(self.settings.value("canvas_size_index", 5))
self.size_combo.setCurrentIndex(size_index)
# Upscale method
method_index = int(self.settings.value("upscale_method_index", 0))
self.method_combo.setCurrentIndex(method_index)
def _save_settings(self):
"""Save current settings."""
self.settings.setValue("output_dir", str(self.converter.output_dir))
self.settings.setValue("canvas_size_index", self.size_combo.currentIndex())
self.settings.setValue("upscale_method_index", self.method_combo.currentIndex())
def _detect_subfolders(self):
"""Detect version subfolders in the cache directory."""
self.subfolder_combo.clear()
if not self.base_cache_path.exists():
self.cache_label.setText(f"Not found: {self.base_cache_path}")
self.status_label.setText("Cache folder not found - check if path is correct")
self.cache_label.setText(f"Not found: {self.base_cache_path}")
self.status_label.setText("Cache folder not found - is Entropia Universe installed?")
return
# Find all subfolders that contain TGA files
@ -504,7 +502,7 @@ class IconExtractorWindow(QMainWindow):
subfolders.append((item.name, tga_count, item))
if not subfolders:
self.cache_label.setText(f"⚠️ No subfolders with TGA files in {self.base_cache_path}")
self.cache_label.setText(f"No subfolders with icons in {self.base_cache_path}")
self.status_label.setText("No version folders found")
return
@ -517,10 +515,10 @@ class IconExtractorWindow(QMainWindow):
# Add "All folders" option at top
total_icons = sum(s[1] for s in subfolders)
self.subfolder_combo.insertItem(0, f"📁 All Folders ({total_icons} icons)", "all")
self.subfolder_combo.insertItem(0, f"All Folders ({total_icons} icons)", "all")
self.subfolder_combo.setCurrentIndex(0)
self.cache_label.setText(f"{self.base_cache_path}")
self.cache_label.setText(f"{self.base_cache_path}")
self.status_label.setText(f"Found {len(subfolders)} version folders")
# Load files
@ -535,23 +533,18 @@ class IconExtractorWindow(QMainWindow):
self.subfolder_combo.setEnabled(not self.all_subfolders_check.isChecked())
self._refresh_file_list()
def _auto_scan(self):
"""Auto-detect cache folder - just refreshes subfolder list."""
self.status_label.setText("Scanning for version folders...")
self._detect_subfolders()
def _browse_cache(self):
"""Browse for cache folder."""
def _browse_output(self):
"""Browse for output folder."""
folder = QFileDialog.getExistingDirectory(
self,
"Select Entropia Universe Cache Folder",
str(self.base_cache_path.parent)
"Select Output Folder",
str(self.converter.output_dir)
)
if folder:
self.base_cache_path = Path(folder)
self.cache_label.setText(str(self.base_cache_path))
self._detect_subfolders()
self.converter.output_dir = Path(folder)
self.output_label.setText(folder)
self._save_settings()
def _refresh_file_list(self):
"""Refresh the list of found files based on current selection."""
@ -602,19 +595,6 @@ class IconExtractorWindow(QMainWindow):
self.convert_btn.setEnabled(len(tga_files) > 0)
def _browse_output(self):
"""Browse for output folder."""
folder = QFileDialog.getExistingDirectory(
self,
"Select Output Folder",
str(self.converter.output_dir)
)
if folder:
self.converter.output_dir = Path(folder)
self.output_label.setText(folder)
self._save_settings()
def _start_conversion(self):
"""Start batch conversion."""
# Get selected files or all files
@ -628,30 +608,21 @@ class IconExtractorWindow(QMainWindow):
files_to_convert = self.found_files
if not files_to_convert:
QMessageBox.warning(self, "No Files", "No files selected for conversion.")
QMessageBox.warning(self, "No Files", "No files selected for extraction.")
return
# Get settings
canvas_size = self.size_combo.currentData()
upscale_method = self.method_combo.currentData()
# Save settings
self._save_settings()
# Setup UI
self.convert_btn.setEnabled(False)
self.convert_btn.setText("⏳ Converting...")
self.convert_btn.setText("Extracting...")
self.progress_bar.setRange(0, len(files_to_convert))
self.progress_bar.setValue(0)
self.progress_bar.setVisible(True)
# Start worker
self.worker = ConversionWorker(
files_to_convert,
self.converter,
canvas_size,
upscale_method
)
self.worker = ConversionWorker(files_to_convert, self.converter)
self.worker.progress.connect(self._on_progress)
self.worker.file_done.connect(self._on_file_done)
self.worker.finished.connect(self._on_finished)
@ -663,28 +634,29 @@ class IconExtractorWindow(QMainWindow):
self.progress_bar.setValue(self.progress_bar.value() + 1)
def _on_file_done(self, filename: str, output_path: str):
logger.info(f"Converted: {filename} -> {output_path}")
logger.info(f"Extracted: {filename} -> {output_path}")
def _on_finished(self, success: int, total: int):
self.convert_btn.setEnabled(True)
self.convert_btn.setText("🚀 Convert All Icons")
self.convert_btn.setText("Start Extracting Icons")
self.progress_bar.setVisible(False)
self.status_label.setText(f"✅ Converted {success}/{total} files")
self.status_label.setText(f"Extracted {success}/{total} icons")
QMessageBox.information(
self,
"Conversion Complete",
f"Successfully converted {success} of {total} icons.\n\n"
f"Output folder:\n{self.converter.output_dir}"
"Extraction Complete",
f"Successfully extracted {success} of {total} icons.\n\n"
f"Output location:\n{self.converter.output_dir}\n\n"
f"Submit these icons to EntropiaNexus.com to help the community!"
)
def _on_error(self, error_msg: str):
self.convert_btn.setEnabled(True)
self.convert_btn.setText("🚀 Convert All Icons")
self.convert_btn.setText("Start Extracting Icons")
self.progress_bar.setVisible(False)
self.status_label.setText(f"Error: {error_msg}")
self.status_label.setText(f"Error: {error_msg}")
QMessageBox.critical(self, "Error", f"Conversion failed:\n{error_msg}")
QMessageBox.critical(self, "Error", f"Extraction failed:\n{error_msg}")
def _open_output_folder(self):
"""Open output folder in file manager."""
@ -709,19 +681,38 @@ class IconExtractorWindow(QMainWindow):
event.accept()
def set_app_icon(app: QApplication):
"""Set application icon."""
# Try to load icon from various locations
icon_paths = [
Path(__file__).parent / "assets" / "icon.ico",
Path(__file__).parent / "assets" / "icon.png",
Path(__file__).parent / "icon.ico",
Path(__file__).parent / "icon.png",
]
for icon_path in icon_paths:
if icon_path.exists():
app.setWindowIcon(QIcon(str(icon_path)))
return
# If no icon file, we can't set one programmatically
# User would need to provide an icon file
def main():
"""Main entry point."""
if not PYQT_AVAILABLE:
print("ERROR: PyQt6 is required. Install with: pip install PyQt6")
sys.exit(1)
if not PIL_AVAILABLE:
print("ERROR: Pillow is required. Install with: pip install Pillow")
sys.exit(1)
app = QApplication(sys.argv)
app.setStyle('Fusion')
# Set application info for proper Windows taskbar icon
app.setApplicationName(APP_NAME)
app.setApplicationVersion(APP_VERSION)
app.setOrganizationName("ImpulsiveFPS")
# Try to set icon
set_app_icon(app)
# Dark theme
app.setStyleSheet("""
QMainWindow, QDialog {
@ -777,6 +768,10 @@ def main():
QProgressBar::chunk {
background-color: #4caf50;
}
QTextEdit {
background-color: #222;
border: 1px solid #444;
}
""")
window = IconExtractorWindow()