initial commit

This commit is contained in:
CPTN Cosmo 2026-04-09 18:26:32 +02:00
commit 45fab1c51d
17 changed files with 15599 additions and 0 deletions

328
main.py Executable file
View file

@ -0,0 +1,328 @@
import sys
import os
from pathlib import Path
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QLabel, QVBoxLayout, QHBoxLayout,
QWidget, QProgressBar, QMessageBox, QPushButton
)
from PyQt6.QtCore import Qt, QThread, pyqtSignal
from PyQt6.QtGui import QDragEnterEvent, QDropEvent
from PIL import Image, ImageOps
# Disable decompression bomb limit for massive wallpapers
Image.MAX_IMAGE_PIXELS = None
# Target resolutions
RESOLUTIONS = {
# Desktop Full HD & QHD & 4K
"1920x1080": (1920, 1080),
"2560x1440": (2560, 1440),
"3840x2160": (3840, 2160),
# Desktop Ultrawide
"2560x1080": (2560, 1080), # UW FHD
"3440x1440": (3440, 1440), # UW QHD
"5120x2160": (5120, 2160), # UW 4K
# Mobile
"1080x1920": (1080, 1920),
"1284x2778": (1284, 2778),
"1440x2560": (1440, 2560),
}
class ExporterWorker(QThread):
progress_updated = pyqtSignal(int)
status_updated = pyqtSignal(str)
finished_success = pyqtSignal(str)
finished_error = pyqtSignal(str)
def __init__(self, image_paths: list[Path]):
super().__init__()
self.image_paths = image_paths
def run(self):
try:
total_tasks = len(self.image_paths) * len(RESOLUTIONS)
completed = 0
output_dirs = []
for path in self.image_paths:
self.status_updated.emit(f"Loading {path.name}...")
original_image = Image.open(path)
# Convert to RGB if it's not (e.g. RGBA or P) to save cleanly as JPEG/PNG
# while keeping background black if there's transparency
if original_image.mode in ('RGBA', 'LA') or (original_image.mode == 'P' and 'transparency' in original_image.info):
bg = Image.new('RGB', original_image.size, (0, 0, 0))
if original_image.mode == 'P':
original_image = original_image.convert('RGBA')
bg.paste(original_image, mask=original_image.split()[3])
original_image = bg
elif original_image.mode != 'RGB':
original_image = original_image.convert('RGB')
original_w, original_h = original_image.size
# Create output directory
output_dir = path.parent / path.stem
output_dir.mkdir(parents=True, exist_ok=True)
if str(output_dir) not in output_dirs:
output_dirs.append(str(output_dir))
for name, (w, h) in RESOLUTIONS.items():
self.status_updated.emit(f"Generating {name}...")
# Check if target is larger than original to avoid upscaling
if w > original_w or h > original_h:
pass # skipping
else:
resized = ImageOps.fit(original_image, (w, h), method=Image.Resampling.LANCZOS, centering=(0.5, 0.5))
output_path = output_dir / f"{path.stem}_{name}.jpg"
resized.save(output_path, "JPEG", quality=95)
completed += 1
self.progress_updated.emit(int((completed / total_tasks) * 100))
original_image.close()
self.status_updated.emit("Done!")
out_msg = "\n".join(output_dirs[:3])
if len(output_dirs) > 3:
out_msg += f"\n... and {len(output_dirs)-3} more directory(s)."
self.finished_success.emit(out_msg)
except Exception as e:
import traceback
traceback.print_exc()
self.finished_error.emit(str(e))
class DropZone(QLabel):
file_dropped = pyqtSignal(list)
def __init__(self):
super().__init__()
self.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.setText("\n\nDrop Image(s)\nHere\n\n")
self.setStyleSheet("""
QLabel {
border: 2px dashed #aaa;
border-radius: 8px;
background-color: #2a2a2a;
color: #ffffff;
font-size: 16px;
font-weight: bold;
}
QLabel:hover {
border-color: #4a90e2;
background-color: #333333;
}
""")
self.setAcceptDrops(True)
def dragEnterEvent(self, event: QDragEnterEvent):
if event.mimeData().hasUrls():
urls = event.mimeData().urls()
if urls:
has_image = any(Path(u.toLocalFile()).suffix.lower() in ['.png', '.jpg', '.jpeg', '.webp', '.bmp'] for u in urls)
if has_image:
event.acceptProposedAction()
self.setStyleSheet("""
QLabel {
border: 2px dashed #4a90e2;
border-radius: 8px;
background-color: #384252;
color: #ffffff;
font-size: 16px;
font-weight: bold;
}
""")
return
event.ignore()
def dragLeaveEvent(self, event):
self.setStyleSheet("""
QLabel {
border: 2px dashed #aaa;
border-radius: 8px;
background-color: #2a2a2a;
color: #ffffff;
font-size: 16px;
font-weight: bold;
}
QLabel:hover {
border-color: #4a90e2;
background-color: #333333;
}
""")
def dropEvent(self, event: QDropEvent):
self.dragLeaveEvent(None) # reset style
paths = []
for url in event.mimeData().urls():
p = Path(url.toLocalFile())
if p.suffix.lower() in ['.png', '.jpg', '.jpeg', '.webp', '.bmp']:
paths.append(p)
if paths:
self.file_dropped.emit(paths)
class TitleBar(QWidget):
def __init__(self, parent):
super().__init__(parent)
self.setFixedHeight(40)
layout = QHBoxLayout(self)
layout.setContentsMargins(15, 0, 5, 0)
from PyQt6.QtGui import QIcon
self.icon_label = QLabel()
icon = QIcon.fromTheme("preferences-desktop-wallpaper", QIcon.fromTheme("image-x-generic"))
self.icon_label.setPixmap(icon.pixmap(20, 20))
self.title = QLabel("Wallpaper Exporter")
self.title.setStyleSheet("color: #ffffff; font-weight: bold; font-size: 14px;")
self.close_btn = QPushButton("")
self.close_btn.setFixedSize(30, 30)
self.close_btn.setStyleSheet("""
QPushButton {
background-color: transparent;
color: #aaaaaa;
border: none;
font-size: 16px;
border-radius: 4px;
}
QPushButton:hover {
background-color: #e81123;
color: white;
}
""")
self.close_btn.clicked.connect(parent.close)
layout.addWidget(self.icon_label)
layout.addSpacing(5)
layout.addWidget(self.title)
layout.addStretch()
layout.addWidget(self.close_btn)
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
window = self.window().windowHandle()
if window:
window.startSystemMove()
event.accept()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowFlag(Qt.WindowType.FramelessWindowHint)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.setFixedSize(450, 350)
# Central widget with custom styling for rounded corners
central_widget = QWidget()
central_widget.setObjectName("MainWidget")
central_widget.setStyleSheet("""
QWidget#MainWidget {
background-color: #2d2d2d;
border-radius: 10px;
border: 1px solid #444444;
}
""")
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
self.title_bar = TitleBar(self)
layout.addWidget(self.title_bar)
# Content frame ensures even padding
content_frame = QWidget()
content_layout = QVBoxLayout(content_frame)
content_layout.setContentsMargins(25, 25, 25, 25)
content_layout.setSpacing(15)
self.drop_zone = DropZone()
self.drop_zone.setFixedWidth(350)
self.drop_zone.setSizePolicy(
self.drop_zone.sizePolicy().Policy.Fixed,
self.drop_zone.sizePolicy().Policy.Expanding
)
self.drop_zone.file_dropped.connect(self.start_processing)
drop_layout = QHBoxLayout()
drop_layout.addStretch()
drop_layout.addWidget(self.drop_zone)
drop_layout.addStretch()
content_layout.addLayout(drop_layout)
self.status_label = QLabel("Ready")
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.status_label.hide()
content_layout.addWidget(self.status_label)
self.progress_bar = QProgressBar()
self.progress_bar.setValue(0)
self.progress_bar.setFixedHeight(12)
self.progress_bar.hide()
content_layout.addWidget(self.progress_bar)
layout.addWidget(content_frame)
self.worker = None
def start_processing(self, file_paths: list):
if len(file_paths) == 1:
self.status_label.setText(f"Preparing: {file_paths[0].name}")
else:
self.status_label.setText(f"Preparing {len(file_paths)} files...")
self.status_label.show()
self.progress_bar.show()
self.progress_bar.setValue(0)
self.drop_zone.setDisabled(True)
self.worker = ExporterWorker(file_paths)
self.worker.progress_updated.connect(self.progress_bar.setValue)
self.worker.status_updated.connect(self.status_label.setText)
self.worker.finished_success.connect(self.processing_finished)
self.worker.finished_error.connect(self.processing_failed)
self.worker.start()
def processing_finished(self, out_dir: str):
self.status_label.hide()
self.progress_bar.hide()
self.drop_zone.setDisabled(False)
QMessageBox.information(self, "Success", f"Images successfully exported to:\n{out_dir}")
def processing_failed(self, error_msg: str):
self.status_label.hide()
self.progress_bar.hide()
self.drop_zone.setDisabled(False)
QMessageBox.critical(self, "Error", f"Failed to export image:\n{error_msg}")
def main():
app = QApplication(sys.argv)
app.setStyle("Fusion")
# Optional dark mode palette for better look out-of-the-box
from PyQt6.QtGui import QPalette, QColor
palette = QPalette()
palette.setColor(QPalette.ColorRole.Window, QColor(45, 45, 45))
palette.setColor(QPalette.ColorRole.WindowText, QColor(255, 255, 255))
app.setPalette(palette)
window = MainWindow()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()