Compare commits
5 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 08b6937142 | |||
| 547dec03a7 | |||
| f93e4cfc6b | |||
| 42e7a473ec | |||
| 21378f2f5b |
4 changed files with 217 additions and 475 deletions
331
CHANGELOG.md
331
CHANGELOG.md
|
|
@ -1,331 +0,0 @@
|
||||||
# Changelog
|
|
||||||
|
|
||||||
## 1.8.x
|
|
||||||
### 1.8.0: Feature - Adding wiki and reform readme
|
|
||||||
- Trimmed readme and moved some content to the wiki.
|
|
||||||
- About now linkes to Forgejo not GitLab, and the new wiki.
|
|
||||||
- pip readme also contains link to wiki.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1.7.x
|
|
||||||
### 1.7.0: Feature - Automatically Add Missing EXIF Fields
|
|
||||||
- Between versions 1.5 and 1.6, two new EXIF fields were introduced: `developer` and `time`.
|
|
||||||
- If these fields were missing in the local EXIF file, the program would still load but crash when processing an image.
|
|
||||||
- Previously, this could be fixed manually by adding the missing fields, something I could easily do myself, but not ideal for other users.
|
|
||||||
- The program now automatically checks if the local EXIF file has these fields, and if not, it adds them with default values.
|
|
||||||
- This might make it easier in the future to adjust local files.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1.6.x
|
|
||||||
### 1.6.0: Feature - Add Information from Developing Process
|
|
||||||
|
|
||||||
- Added two new combo boxes: one for **film developer** and one for **development time** (how long the film was developed).
|
|
||||||
- The film developer field accepts all characters, while the time field only accepts `NA` or `mm:ss`.
|
|
||||||
- **Improved EXIF Edit Window**: Pressing **Enter** (Return) now adds the new item to the list, just like pressing the **Add** button.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1.5.x
|
|
||||||
### 1.5.0: Feature - Time of dateEdit now today
|
|
||||||
- Changes that instead of the dateEdit elements being always set to a last day of 2024 it is the current day.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1.4.x
|
|
||||||
### 1.4.2: Fix links
|
|
||||||
- Fixed that changelog was linked to GitLab, not it is to code.boxyfoxy.net
|
|
||||||
- Fixed Changelog
|
|
||||||
|
|
||||||
### 1.4.1: Fix CI
|
|
||||||
- Fixed pipline
|
|
||||||
### 1.4.0: New CI
|
|
||||||
- Migrated repo from GitLab to my forgejo instance, therefore switching to woodpecker CI
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1.3.x
|
|
||||||
### 1.3.4: Fix - Spelling (25.04.01)
|
|
||||||
- Fixed misspelling in preview window.
|
|
||||||
|
|
||||||
### 1.3.3: Patch - Increased Preview Performance x2 (25.04.01)
|
|
||||||
- Reduced preview stutter: Previously, the image was updated twice when changing brightness or contrast. Now, it updates only once, improving performance by 100%.
|
|
||||||
- There is still room for improvement. Analysis shows that image processing takes the most time, while displaying in Qt is relatively fast. Reducing the image size impacts performance, so resizing to 50% is a good idea.
|
|
||||||
- There is an issue where `QRunner` does not always finish in the correct order when brightness or contrast values are changed rapidly. ATM I do not know how to fix this easly.
|
|
||||||
- The "Preview" watermark now displays the brightness and contrast levels of the shown image.
|
|
||||||
|
|
||||||
### 1.3.2: Fix - Fixed problem with app folders (25.03.30)
|
|
||||||
- Fixed a problem that the app folder path was not generated correctly.
|
|
||||||
|
|
||||||
### 1.3.1: Fix - Fixed insert exif not working (25.03.27)
|
|
||||||
- Fixed the feature that inserted exif into images without modifying them.
|
|
||||||
|
|
||||||
### 1.3.0: Feature - Write Adjustments into EXIF (25.03.24)
|
|
||||||
- Changes to contrast and brightness are now recorded in the EXIF comment section (labeled *Scanner* in the program).
|
|
||||||
- This allows users to track what adjustments were made to an image.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1.2.x
|
|
||||||
### 1.2.2: Patch - Pyproject file (25.03.23)
|
|
||||||
- Fixed `Development Status` Classifier
|
|
||||||
- Added <4.0 python version
|
|
||||||
|
|
||||||
### 1.2.1: Patch - Changelog file (25.03.23)
|
|
||||||
- Patches formation in changelog file.
|
|
||||||
|
|
||||||
### 1.2.0: Refactor - Splitting Classes into Separate Files (25.03.23)
|
|
||||||
- Refactored `gui.py`, which previously contained almost all the code, into multiple files.
|
|
||||||
- Each window's logic is now in its own file, improving code organization.
|
|
||||||
- Window layouts remain in `.ui` folder, while their logic is now properly separated.
|
|
||||||
|
|
||||||
## 1.1.x
|
|
||||||
### 1.1.0: Feature - New Function in Preview Window (25.03.23)
|
|
||||||
- Added a new feature to the preview window: **Hold a button to temporarily view the original (unedited) image.** This makes it easier to compare changes.
|
|
||||||
- Minor UI adjustments.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1.0.x (25.03.06)
|
|
||||||
### 1.0.1: Fixed spelling
|
|
||||||
- Fixes spelling some places
|
|
||||||
|
|
||||||
### 1.0.0: Fix version bump
|
|
||||||
- Version was not bumped correctly
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0.15.1: Final Polish (25.03.06)
|
|
||||||
- Fixed a bug where the GPS field being empty but selected caused issues.
|
|
||||||
- EXIF insertion is now canceled if any image in the folder does not end with a number.
|
|
||||||
- Minor GUI adjustments for a more polished experience.
|
|
||||||
- Disabled preview adjustment controls until an image is loaded to prevent errors.
|
|
||||||
|
|
||||||
## 0.15.0: Preview Image Resizing Update (25.02.12)
|
|
||||||
- Added the ability to change the preview image size dynamically.
|
|
||||||
- Previously, the image was processed and displayed at its original size, causing lag or unresponsiveness when adjusting the slider.
|
|
||||||
- Reducing the processed image size should help improve performance.
|
|
||||||
- Default preview image size is now **25%**, but users can adjust it between **10% and 100%**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0.14.x
|
|
||||||
### 0.14.1: Patch changelog (25.02.12)
|
|
||||||
- Updated the changelog to include missing entries.
|
|
||||||
|
|
||||||
### 0.14.0: Code refactor (by Mr. Finch) (25.02.11)
|
|
||||||
- Introduced constants and optimized the code.
|
|
||||||
|
|
||||||
## 0.13.x (25.02.11)
|
|
||||||
### 0.13.1: Fixed image processing
|
|
||||||
- Fixed a bug that made it impossible to process images.
|
|
||||||
|
|
||||||
### 0.13.0: Requirements file (by Mr. Finch)
|
|
||||||
- Added a requirements file.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0.12.x
|
|
||||||
### 0.12.6: Disabled app restart on Windows (25.02.11)
|
|
||||||
- The app can not restart properly on Windows, so the restart button has been hidden when the OS is `nt`.
|
|
||||||
- Also updated tool tip to indicate that changing theme requeres are restart.
|
|
||||||
|
|
||||||
### 0.12.5: Fixed EXIF File Generation Bug (25.02.10)
|
|
||||||
- Fixed a bug where the application failed to generate a new default EXIF file if none was available. Now, the file is correctly created when missing.
|
|
||||||
|
|
||||||
### 0.12.4: Updated README (25.02.10)
|
|
||||||
- The README file (project description) now includes updates description and screenshots.
|
|
||||||
|
|
||||||
### 0.12.3: UI Adjustments (25.02.10)
|
|
||||||
- Minor changes to maintain a unified layout across all windows.
|
|
||||||
- Added option to sync app theme with OS (if supported).
|
|
||||||
- Set auto theme as the default value.
|
|
||||||
|
|
||||||
### 0.12.2: Minor UI Improvements for Theme Compatibility (25.02.10)
|
|
||||||
- Fixed text clipping issues when using the new theme options.
|
|
||||||
|
|
||||||
### 0.12.1: Removed Unnecessary Debug Prints (25.02.09)
|
|
||||||
- Removed leftover debug statements.
|
|
||||||
|
|
||||||
### 0.12.0: New Settings Menu & Patches (25.02.09)
|
|
||||||
- **New Settings Window:**
|
|
||||||
- The updater window has been reworked into a settings window.
|
|
||||||
- **Initial settings (first tab) include:**
|
|
||||||
1. Option to change the theme (with an optional dependency installation).
|
|
||||||
2. Reset selectable EXIF data to default.
|
|
||||||
- The updater UI has been moved to the second tab.
|
|
||||||
- Added a link to the changelog for easier access to update details.
|
|
||||||
|
|
||||||
- **Patches:**
|
|
||||||
- Fixed an issue where links in labels (About window) did not open a browser.
|
|
||||||
- Added a changelog link in the About window.
|
|
||||||
- Minor changes to `utility.py` to handle settings.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0.11.x (25.02.05)
|
|
||||||
### 0.11.1: Fixed pipeline
|
|
||||||
- Fixed pipeline publish error
|
|
||||||
|
|
||||||
### 0.11.0: Refactor and Patches
|
|
||||||
- Fixed an issue with the updater: The updater window wouldn't start if the `updater_log.json` file was missing or lacked a valid last `time.time()` float value.
|
|
||||||
- Corrected layout issues in the preview window, repositioning elements to their proper places.
|
|
||||||
- Added an application icon (may not work on all desktop environments).
|
|
||||||
- Refactored code to reduce the size of the PyPi package by removing unnecessary folders.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0.10.x (25.02.04)
|
|
||||||
### 0.10.1: Fixed Updater
|
|
||||||
- Fixed an issue where the updater was permanently disabled.
|
|
||||||
|
|
||||||
### 0.10.0: Multithreading for Preview Window
|
|
||||||
- The preview window now processes images in a separate thread, and live update preview is enabled by default.
|
|
||||||
- This improves UI responsiveness.
|
|
||||||
- The image now resizes dynamically to fit the window when the window size changes.
|
|
||||||
- Minor UI improvements.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0.9.x
|
|
||||||
### 0.9.2: Enhanced updater
|
|
||||||
- Minor enhancments for the updater
|
|
||||||
|
|
||||||
### 0.9.1: Patch for Unsuccessful Successful Update
|
|
||||||
- Addressed a rare issue where the package did not update correctly using the updater.
|
|
||||||
- Unable to reproduce, but it may have been related to an older version and the restart process.
|
|
||||||
- Added developer functions to test the updater without requiring a published release.
|
|
||||||
|
|
||||||
### 0.9.0: UI Enhancements and Language Refinements
|
|
||||||
- Changed text, labels, buttons, and checkboxes for clearer understanding.
|
|
||||||
- Improved UI language to make the interface easier to navigate.
|
|
||||||
- Added tooltips for more helpful information and guidance.
|
|
||||||
- Updates applied across the main window (both tabs) and the preview window.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0.8.x
|
|
||||||
### 0.8.5: Patch for New PyPiUpdater Version
|
|
||||||
- **PyPiUpdater 0.5** introduced breaking changes; adjusted code to ensure compatibility with the new version.
|
|
||||||
|
|
||||||
### 0.8.4: Minor Enhancements & Cleanup
|
|
||||||
- Updated window titles.
|
|
||||||
- Improved error handling for updater: now displays the specific error message instead of just **"error"** when an issue occurs during update checks.
|
|
||||||
- Ensured all child windows close when the main window is closed.
|
|
||||||
|
|
||||||
### 0.8.3: Fix - OptimaLab35 Not Closing After Update
|
|
||||||
- Fixed an issue where **OptimaLab35** would not close properly when updating, resulting in an unresponsive instance and multiple running processes.
|
|
||||||
|
|
||||||
### 0.8.2: Patch for New PyPiUpdater Version
|
|
||||||
- Updated to support **PyPiUpdater 0.4.0**.
|
|
||||||
- Now stores version information locally, preventing an "unknown" state on the first updater launch.
|
|
||||||
- Users still need to press the **Update** button to verify the latest version, ensuring an internet connection is available.
|
|
||||||
|
|
||||||
### 0.8.1: Fix
|
|
||||||
- Fixed a misspelling of `PyPiUpdater` in the build file, which prevented v0.8.0 from being installed.
|
|
||||||
|
|
||||||
### 0.8.0: Updater Feature
|
|
||||||
- Added an updater function utilizing my new package [PyPiUpdater](https://gitlab.com/CodeByMrFinchum/PyPiUpdater).
|
|
||||||
- New updater window displaying the local version and checking for updates online.
|
|
||||||
- Added an option to update and restart the app from the menu.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0.7.0: Enhanced Preview
|
|
||||||
- Images loaded into the preview window are now scaled while maintaining aspect ratio.
|
|
||||||
- Added live updates: changes to brightness, contrast, or grayscale are applied immediately.
|
|
||||||
- This may crush the system depending on image size and system specifications.
|
|
||||||
- Removed Settings from menuBar, and extended the about window.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0.6.0: Initial Flatpak Support
|
|
||||||
- Started Flatpak package building.
|
|
||||||
- Not added to Flathub yet, as only stable software is hosted there.
|
|
||||||
- Not fully completed, icon, name, and description are included, but the version is missing for some reason.
|
|
||||||
- Local build and installation work. The Bash script `build_flatpak.sh` in the `flatpak/` directory generates all pip dependencies, then builds and installs the app locally.
|
|
||||||
- `requirements-parser` has to be installed from pip to finish installing the flatpak (maybe more pypi packages..)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0.5.0
|
|
||||||
- Removed all leftover of tui code that was hiding in some classes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0.4.0
|
|
||||||
- Fixed a critical issue that prevented the program from functioning.
|
|
||||||
- Updated compatibility to align with the **upcoming** optima35 **release**.
|
|
||||||
|
|
||||||
**Removal of TUI:**
|
|
||||||
- The TUI version is no longer compatible with optima35 v1.0.
|
|
||||||
- Maintaining two UIs has become too time-consuming, as the primary focus is on the GUI, which provides the best user experience. Recently, the TUI version was only receiving patches without any meaningful enhancements.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0.3.x
|
|
||||||
### 0.3.7: prepear for optima35 release
|
|
||||||
- Added a maximum version of dependencies list.
|
|
||||||
|
|
||||||
### 0.3.6: Patch
|
|
||||||
- Added check if any exif options are empty.
|
|
||||||
- Also made the exif editor aviable without checking the exif box.
|
|
||||||
|
|
||||||
### 0.3.5: Fix
|
|
||||||
- Fixed an issue where renaming images, while converting could result in wrong numbering.
|
|
||||||
|
|
||||||
### 0.3.1 - 0.3.4
|
|
||||||
- Repo only: Fix building pipeline
|
|
||||||
|
|
||||||
### 0.3.0
|
|
||||||
- Repo only: adding pipeline
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0.2.x
|
|
||||||
### 0.2.3
|
|
||||||
- Refactored code for improved readability.
|
|
||||||
|
|
||||||
### 0.2.2
|
|
||||||
- Moved processing images into a different thread, making the UI responsiable while processing
|
|
||||||
|
|
||||||
### 0.2.1
|
|
||||||
- Insert exif to image file (i.e. without changing the file).
|
|
||||||
|
|
||||||
### 0.2.0
|
|
||||||
- Now spaces in rename string are replaces with `_`.
|
|
||||||
- version check of `optima35`, incase pip did not update it.
|
|
||||||
- Sorting entries from exif file.
|
|
||||||
|
|
||||||
### 0.2.0-a1
|
|
||||||
- Main UI received a facelift.
|
|
||||||
- Added a new experimental preview window to display an image and show how changing values affects it.
|
|
||||||
- Programm now warns for potential overwrite of existing files.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0.1.x
|
|
||||||
### 0.1.1
|
|
||||||
- Update metadata, preview, readme, bump in version for pip
|
|
||||||
|
|
||||||
### 0.1.0
|
|
||||||
- Preserved the current working GUI by pinning `optima35` to a specific version for guaranteed compatibility.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0.0.x
|
|
||||||
### 0.0.4-a2
|
|
||||||
- Adding __version__ to `__init__.py` so version is automaticly updated in program as well as pypi.
|
|
||||||
|
|
||||||
### 0.0.4-a1
|
|
||||||
- Refactored project structure, moving all code to the `src` directory.
|
|
||||||
- Adjusted imports and setup to accommodate the new folder structure.
|
|
||||||
- Skipped version numbers to `.4` due to PyPI versioning constraints (testing purposes).
|
|
||||||
|
|
||||||
### 0.0.1 - Initial UI-Focused Release
|
|
||||||
- Forked from OPTIMA35.
|
|
||||||
- Removed core OPTIMA35 files to focus exclusively on UI components.
|
|
||||||
- Integrated OPTIMA35 functionality via the pip package.
|
|
||||||
- Ensured both TUI and GUI modes operate seamlessly.
|
|
||||||
- Revised the README for improved clarity and structure.
|
|
||||||
|
|
@ -1,42 +1,31 @@
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from optima35.core import OptimaManager
|
from optima35.core import OptimaManager
|
||||||
|
from PySide6 import QtCore, QtWidgets
|
||||||
from OptimaLab35 import __version__
|
|
||||||
from .const import (
|
|
||||||
APPLICATION_NAME,
|
|
||||||
CONFIG_BASE_PATH
|
|
||||||
)
|
|
||||||
|
|
||||||
from .ui import resources_rc
|
|
||||||
from .previewWindow import PreviewWindow
|
|
||||||
from .settingsWindow import SettingsWindow
|
|
||||||
|
|
||||||
from .utils.utility import Utilities
|
|
||||||
from .ui.main_window import Ui_MainWindow
|
|
||||||
from .ui.exif_handler_window import ExifEditor
|
|
||||||
from .ui.simple_dialog import SimpleDialog # Import the SimpleDialog class
|
|
||||||
|
|
||||||
from PySide6 import QtWidgets, QtCore
|
|
||||||
|
|
||||||
from PySide6.QtCore import (
|
from PySide6.QtCore import (
|
||||||
QRunnable,
|
QDate,
|
||||||
QThreadPool,
|
|
||||||
Signal,
|
|
||||||
QObject,
|
QObject,
|
||||||
QRegularExpression,
|
QRegularExpression,
|
||||||
|
QRunnable,
|
||||||
Qt,
|
Qt,
|
||||||
QDate
|
QThreadPool,
|
||||||
|
Signal,
|
||||||
)
|
)
|
||||||
|
from PySide6.QtGui import QIcon, QRegularExpressionValidator
|
||||||
|
from PySide6.QtWidgets import QApplication, QFileDialog, QMainWindow, QMessageBox
|
||||||
|
|
||||||
from PySide6.QtWidgets import (
|
from OptimaLab35 import __version__
|
||||||
QMessageBox,
|
|
||||||
QApplication,
|
from .const import APPLICATION_NAME, CONFIG_BASE_PATH
|
||||||
QMainWindow,
|
from .previewWindow import PreviewWindow
|
||||||
QFileDialog
|
from .settingsWindow import SettingsWindow
|
||||||
)
|
from .ui import resources_rc
|
||||||
|
from .ui.exif_handler_window import ExifEditor
|
||||||
|
from .ui.main_window import Ui_MainWindow
|
||||||
|
from .ui.simple_dialog import SimpleDialog # Import the SimpleDialog class
|
||||||
|
from .utils.utility import Utilities
|
||||||
|
|
||||||
from PySide6.QtGui import QRegularExpressionValidator, QIcon
|
|
||||||
|
|
||||||
class OptimaLab35(QMainWindow, Ui_MainWindow):
|
class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -46,7 +35,7 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
self.o = OptimaManager()
|
self.o = OptimaManager()
|
||||||
self.u = Utilities(os.path.expanduser(CONFIG_BASE_PATH))
|
self.u = Utilities(os.path.expanduser(CONFIG_BASE_PATH))
|
||||||
self.app_settings = self.u.load_settings()
|
self.app_settings = self.u.load_settings()
|
||||||
self.thread_pool = QThreadPool() # multi thread ChatGPT
|
self.thread_pool = QThreadPool() # multi thread ChatGPT
|
||||||
# Initiate internal object
|
# Initiate internal object
|
||||||
self.exif_file = os.path.expanduser(f"{CONFIG_BASE_PATH}/exif.yaml")
|
self.exif_file = os.path.expanduser(f"{CONFIG_BASE_PATH}/exif.yaml")
|
||||||
self.available_exif_data = None
|
self.available_exif_data = None
|
||||||
|
|
@ -98,6 +87,7 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
self.ui.lat_lineEdit.setValidator(validator)
|
self.ui.lat_lineEdit.setValidator(validator)
|
||||||
self.ui.long_lineEdit.setValidator(validator)
|
self.ui.long_lineEdit.setValidator(validator)
|
||||||
self.ui.dateEdit.setDate(QDate.currentDate())
|
self.ui.dateEdit.setDate(QDate.currentDate())
|
||||||
|
|
||||||
# UI related function, changing parts, open, etc.
|
# UI related function, changing parts, open, etc.
|
||||||
def open_preview_window(self):
|
def open_preview_window(self):
|
||||||
self.preview_window = PreviewWindow()
|
self.preview_window = PreviewWindow()
|
||||||
|
|
@ -118,7 +108,7 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
def info_window(self):
|
def info_window(self):
|
||||||
info_text = f"""
|
info_text = f"""
|
||||||
<h3>{self.name} v{self.version}</h3>
|
<h3>{self.name} v{self.version}</h3>
|
||||||
<p>(C) 2024-2025 Mr Finchum aka CodeByMrFinchum</p>
|
<p>(C) 2024-2026 Mr Finchum aka CodeByMrFinchum</p>
|
||||||
<p>{self.name} is a GUI for {self.o.name} (v{self.o.version}), enhancing its functionality with a user-friendly interface for efficient image and metadata management.</p>
|
<p>{self.name} is a GUI for {self.o.name} (v{self.o.version}), enhancing its functionality with a user-friendly interface for efficient image and metadata management.</p>
|
||||||
|
|
||||||
<h4>Features:</h4>
|
<h4>Features:</h4>
|
||||||
|
|
@ -132,7 +122,7 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
<p>For more details, visit:</p>
|
<p>For more details, visit:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/wiki">Wiki</a></li>
|
<li><a href="https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/wiki">Wiki</a></li>
|
||||||
<li><a href="https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/src/branch/main/CHANGELOG.md">Changelog</a></li>
|
<li><a href="https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/wiki/Changelog">Changelog</a></li>
|
||||||
<li><a href="https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35">OptimaLab35 Forgejo</a></li>
|
<li><a href="https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35">OptimaLab35 Forgejo</a></li>
|
||||||
<li><a href="https://code.boxyfoxy.net/CodeByMrFinchum/optima35">optima35 Forgejo</a></li>
|
<li><a href="https://code.boxyfoxy.net/CodeByMrFinchum/optima35">optima35 Forgejo</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -185,8 +175,12 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
filtered.append(stripped)
|
filtered.append(stripped)
|
||||||
# ignore anything else
|
# ignore anything else
|
||||||
# Sort: NA first, then valid times ascending
|
# Sort: NA first, then valid times ascending
|
||||||
return sorted(filtered, key=lambda x: (0, 0) if (x is None or str(x).strip().upper() in {"NA"})
|
return sorted(
|
||||||
else (1, self.parse_time(x)))
|
filtered,
|
||||||
|
key=lambda x: (0, 0)
|
||||||
|
if (x is None or str(x).strip().upper() in {"NA"})
|
||||||
|
else (1, self.parse_time(x)),
|
||||||
|
)
|
||||||
|
|
||||||
def sort_dict_of_lists(self, input_dict):
|
def sort_dict_of_lists(self, input_dict):
|
||||||
# Partily ChatGPT
|
# Partily ChatGPT
|
||||||
|
|
@ -204,7 +198,9 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
elif all(isinstance(x, str) for x in lst):
|
elif all(isinstance(x, str) for x in lst):
|
||||||
sorted_dict[key] = sorted(
|
sorted_dict[key] = sorted(
|
||||||
lst,
|
lst,
|
||||||
key=lambda x: (0, x.lower()) if str(x).lower() == "na" else (1, str(x).lower())
|
key=lambda x: (0, x.lower())
|
||||||
|
if str(x).lower() == "na"
|
||||||
|
else (1, str(x).lower()),
|
||||||
)
|
)
|
||||||
|
|
||||||
return sorted_dict
|
return sorted_dict
|
||||||
|
|
@ -246,29 +242,29 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
self.populate_comboboxes(combo_mapping)
|
self.populate_comboboxes(combo_mapping)
|
||||||
|
|
||||||
def update_quality_options(self):
|
def update_quality_options(self):
|
||||||
"""Update visibility of quality settings based on selected format."""
|
"""Update visibility of quality settings based on selected format."""
|
||||||
# Partly ChatGPT
|
# Partly ChatGPT
|
||||||
selected_format = self.ui.image_type.currentText()
|
selected_format = self.ui.image_type.currentText()
|
||||||
# Hide all quality settings
|
# Hide all quality settings
|
||||||
self.ui.png_quality_spinBox.setVisible(False)
|
self.ui.png_quality_spinBox.setVisible(False)
|
||||||
self.ui.jpg_quality_spinBox.setVisible(False)
|
self.ui.jpg_quality_spinBox.setVisible(False)
|
||||||
self.ui.jpg_quality_Slider.setVisible(False)
|
self.ui.jpg_quality_Slider.setVisible(False)
|
||||||
self.ui.png_quality_Slider.setVisible(False)
|
self.ui.png_quality_Slider.setVisible(False)
|
||||||
self.ui.quality_label_1.setVisible(False)
|
self.ui.quality_label_1.setVisible(False)
|
||||||
self.ui.quality_label_2.setVisible(False)
|
self.ui.quality_label_2.setVisible(False)
|
||||||
# Show relevant settings
|
# Show relevant settings
|
||||||
if selected_format == "jpg":
|
if selected_format == "jpg":
|
||||||
self.ui.jpg_quality_spinBox.setVisible(True)
|
self.ui.jpg_quality_spinBox.setVisible(True)
|
||||||
self.ui.jpg_quality_Slider.setVisible(True)
|
self.ui.jpg_quality_Slider.setVisible(True)
|
||||||
self.ui.quality_label_1.setVisible(True)
|
self.ui.quality_label_1.setVisible(True)
|
||||||
elif selected_format == "webp":
|
elif selected_format == "webp":
|
||||||
self.ui.jpg_quality_spinBox.setVisible(True)
|
self.ui.jpg_quality_spinBox.setVisible(True)
|
||||||
self.ui.jpg_quality_Slider.setVisible(True)
|
self.ui.jpg_quality_Slider.setVisible(True)
|
||||||
self.ui.quality_label_1.setVisible(True)
|
self.ui.quality_label_1.setVisible(True)
|
||||||
elif selected_format == "png":
|
elif selected_format == "png":
|
||||||
self.ui.png_quality_spinBox.setVisible(True)
|
self.ui.png_quality_spinBox.setVisible(True)
|
||||||
self.ui.png_quality_Slider.setVisible(True)
|
self.ui.png_quality_Slider.setVisible(True)
|
||||||
self.ui.quality_label_2.setVisible(True)
|
self.ui.quality_label_2.setVisible(True)
|
||||||
|
|
||||||
def browse_input_folder(self):
|
def browse_input_folder(self):
|
||||||
folder = QFileDialog.getExistingDirectory(self, "Select Input Folder")
|
folder = QFileDialog.getExistingDirectory(self, "Select Input Folder")
|
||||||
|
|
@ -280,7 +276,7 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
if folder:
|
if folder:
|
||||||
self.ui.output_path.setText(folder)
|
self.ui.output_path.setText(folder)
|
||||||
|
|
||||||
def change_statusbar(self, msg, timeout = 500):
|
def change_statusbar(self, msg, timeout=500):
|
||||||
self.ui.statusBar.showMessage(msg, timeout)
|
self.ui.statusBar.showMessage(msg, timeout)
|
||||||
|
|
||||||
# Core functions
|
# Core functions
|
||||||
|
|
@ -291,7 +287,9 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
|
|
||||||
def image_list_from_folder(self, path):
|
def image_list_from_folder(self, path):
|
||||||
image_files = [
|
image_files = [
|
||||||
f for f in os.listdir(path) if f.lower().endswith((".png", ".jpg", ".jpeg", ".webp"))
|
f
|
||||||
|
for f in os.listdir(path)
|
||||||
|
if f.lower().endswith((".png", ".jpg", ".jpeg", ".webp"))
|
||||||
]
|
]
|
||||||
image_files.sort()
|
image_files.sort()
|
||||||
return image_files
|
return image_files
|
||||||
|
|
@ -307,11 +305,17 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
|
|
||||||
if process == "image":
|
if process == "image":
|
||||||
if not input_folder or not output_folder:
|
if not input_folder or not output_folder:
|
||||||
QMessageBox.warning(self, "Warning", "Input or output folder not selected")
|
QMessageBox.warning(
|
||||||
|
self, "Warning", "Input or output folder not selected"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not input_folder_valid or not output_folder_valid:
|
if not input_folder_valid or not output_folder_valid:
|
||||||
QMessageBox.warning(self, "Warning", f"Input location {input_folder_valid}\nOutput folder {output_folder_valid}...")
|
QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"Warning",
|
||||||
|
f"Input location {input_folder_valid}\nOutput folder {output_folder_valid}...",
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if len(self.image_list_from_folder(output_folder)) != 0:
|
if len(self.image_list_from_folder(output_folder)) != 0:
|
||||||
|
|
@ -326,7 +330,6 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
elif process == "exif":
|
elif process == "exif":
|
||||||
|
|
||||||
if not input_folder:
|
if not input_folder:
|
||||||
QMessageBox.warning(self, "Warning", "Input not selected")
|
QMessageBox.warning(self, "Warning", "Input not selected")
|
||||||
return False
|
return False
|
||||||
|
|
@ -342,15 +345,19 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
if reply == QMessageBox.No:
|
if reply == QMessageBox.No:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not input_folder_valid :
|
if not input_folder_valid:
|
||||||
QMessageBox.warning(self, "Warning", f"Input location {input_folder_valid}")
|
QMessageBox.warning(
|
||||||
|
self, "Warning", f"Input location {input_folder_valid}"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("Something went wrong")
|
print("Something went wrong")
|
||||||
|
|
||||||
if len(image_list) == 0:
|
if len(image_list) == 0:
|
||||||
QMessageBox.warning(self, "Warning", "Selected folder has no supported files.")
|
QMessageBox.warning(
|
||||||
|
self, "Warning", "Selected folder has no supported files."
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
@ -369,7 +376,9 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
|
|
||||||
image_list = self.image_list_from_folder(self.settings["input_folder"])
|
image_list = self.image_list_from_folder(self.settings["input_folder"])
|
||||||
# Create a worker ChatGPT
|
# Create a worker ChatGPT
|
||||||
worker = ImageProcessorRunnable(image_list, self.settings, self.handle_qprogressbar)
|
worker = ImageProcessorRunnable(
|
||||||
|
image_list, self.settings, self.handle_qprogressbar
|
||||||
|
)
|
||||||
worker.signals.finished.connect(self.on_processing_finished)
|
worker.signals.finished.connect(self.on_processing_finished)
|
||||||
# Start worker in thread pool ChatGPT
|
# Start worker in thread pool ChatGPT
|
||||||
self.thread_pool.start(worker)
|
self.thread_pool.start(worker)
|
||||||
|
|
@ -387,13 +396,13 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
|
|
||||||
i = 1
|
i = 1
|
||||||
for image_file in image_files:
|
for image_file in image_files:
|
||||||
|
|
||||||
input_path = os.path.join(input_folder, image_file)
|
input_path = os.path.join(input_folder, image_file)
|
||||||
|
|
||||||
self.o.insert_exif_to_image(
|
self.o.insert_exif_to_image(
|
||||||
exif_dict = self.settings["user_selected_exif"],
|
exif_dict=self.settings["user_selected_exif"],
|
||||||
image_path = input_path,
|
image_path=input_path,
|
||||||
gps = self.settings["gps"])
|
gps=self.settings["gps"],
|
||||||
|
)
|
||||||
self.change_statusbar(image_file, 100)
|
self.change_statusbar(image_file, 100)
|
||||||
self.handle_qprogressbar(int((i / len(image_files)) * 100))
|
self.handle_qprogressbar(int((i / len(image_files)) * 100))
|
||||||
i += 1
|
i += 1
|
||||||
|
|
@ -415,7 +424,11 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
image_list = self.image_list_from_folder(self.settings["input_folder"])
|
image_list = self.image_list_from_folder(self.settings["input_folder"])
|
||||||
print(image_list)
|
print(image_list)
|
||||||
if not self.control_ending(image_list):
|
if not self.control_ending(image_list):
|
||||||
QMessageBox.warning(self, "Warning", f"Error: one or more filenames do not end on a number.\nCan not adjust time")
|
QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"Warning",
|
||||||
|
f"Error: one or more filenames do not end on a number.\nCan not adjust time",
|
||||||
|
)
|
||||||
self.toggle_buttons(True)
|
self.toggle_buttons(True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -424,19 +437,19 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
self.toggle_buttons(True)
|
self.toggle_buttons(True)
|
||||||
QMessageBox.information(self, "Information", "Finished")
|
QMessageBox.information(self, "Information", "Finished")
|
||||||
|
|
||||||
def get_checkbox_value(self, checkbox, default = None):
|
def get_checkbox_value(self, checkbox, default=None):
|
||||||
"""Helper function to get the value of a checkbox or a default value."""
|
"""Helper function to get the value of a checkbox or a default value."""
|
||||||
return checkbox.isChecked() if checkbox else default
|
return checkbox.isChecked() if checkbox else default
|
||||||
|
|
||||||
def get_spinbox_value(self, spinbox, default = None):
|
def get_spinbox_value(self, spinbox, default=None):
|
||||||
"""Helper function to get the value of a spinbox and handle empty input."""
|
"""Helper function to get the value of a spinbox and handle empty input."""
|
||||||
return int(spinbox.text()) if spinbox.text() else default
|
return int(spinbox.text()) if spinbox.text() else default
|
||||||
|
|
||||||
def get_combobox_value(self, combobox, default = None):
|
def get_combobox_value(self, combobox, default=None):
|
||||||
"""Helper function to get the value of a combobox."""
|
"""Helper function to get the value of a combobox."""
|
||||||
return combobox.currentIndex() + 1 if combobox.currentIndex() != -1 else default
|
return combobox.currentIndex() + 1 if combobox.currentIndex() != -1 else default
|
||||||
|
|
||||||
def get_text_value(self, lineedit, default = None):
|
def get_text_value(self, lineedit, default=None):
|
||||||
"""Helper function to get the value of a text input field."""
|
"""Helper function to get the value of a text input field."""
|
||||||
return lineedit.text() if lineedit.text() else default
|
return lineedit.text() if lineedit.text() else default
|
||||||
|
|
||||||
|
|
@ -464,12 +477,21 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
user_data["lens"] = self.ui.lens_comboBox.currentText()
|
user_data["lens"] = self.ui.lens_comboBox.currentText()
|
||||||
user_data["iso"] = self.ui.iso_comboBox.currentText()
|
user_data["iso"] = self.ui.iso_comboBox.currentText()
|
||||||
lab_info = self.add_laboratory_info()
|
lab_info = self.add_laboratory_info()
|
||||||
user_data["image_description"] = f"{self.ui.image_description_comboBox.currentText()} {lab_info}"
|
user_data["image_description"] = (
|
||||||
|
f"{self.ui.image_description_comboBox.currentText()} {lab_info}"
|
||||||
|
)
|
||||||
user_data["artist"] = self.ui.artist_comboBox.currentText()
|
user_data["artist"] = self.ui.artist_comboBox.currentText()
|
||||||
user_data["copyright_info"] = self.ui.copyright_info_comboBox.currentText()
|
user_data["copyright_info"] = self.ui.copyright_info_comboBox.currentText()
|
||||||
user_data["software"] = f"{self.name} {self.version} with {self.o.name} {self.o.version}"
|
user_data["software"] = (
|
||||||
if int(self.ui.contrast_spinBox.text()) != 0 or int(self.ui.brightness_spinBox.text()) != 0:
|
f"{self.name} {self.version} with {self.o.name} {self.o.version}"
|
||||||
user_data["user_comment"] = f"{self.ui.user_comment_comboBox.currentText()}, contrast: {self.ui.contrast_spinBox.text()}, brightness: {self.ui.brightness_spinBox.text()}"
|
)
|
||||||
|
if (
|
||||||
|
int(self.ui.contrast_spinBox.text()) != 0
|
||||||
|
or int(self.ui.brightness_spinBox.text()) != 0
|
||||||
|
):
|
||||||
|
user_data["user_comment"] = (
|
||||||
|
f"{self.ui.user_comment_comboBox.currentText()}, contrast: {self.ui.contrast_spinBox.text()}, brightness: {self.ui.brightness_spinBox.text()}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
user_data["user_comment"] = self.ui.user_comment_comboBox.currentText()
|
user_data["user_comment"] = self.ui.user_comment_comboBox.currentText()
|
||||||
|
|
||||||
|
|
@ -477,13 +499,18 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
|
|
||||||
def get_selected_exif(self):
|
def get_selected_exif(self):
|
||||||
"""Collect selected EXIF data and handle date and GPS if necessary."""
|
"""Collect selected EXIF data and handle date and GPS if necessary."""
|
||||||
selected_exif = self.collect_selected_exif() if self.ui.exif_checkbox.isChecked() else None
|
selected_exif = (
|
||||||
|
self.collect_selected_exif() if self.ui.exif_checkbox.isChecked() else None
|
||||||
|
)
|
||||||
if selected_exif:
|
if selected_exif:
|
||||||
if self.ui.add_date_checkBox.isChecked():
|
if self.ui.add_date_checkBox.isChecked():
|
||||||
selected_exif["date_time_original"] = self.get_date()
|
selected_exif["date_time_original"] = self.get_date()
|
||||||
if self.ui.gps_checkBox.isChecked():
|
if self.ui.gps_checkBox.isChecked():
|
||||||
try:
|
try:
|
||||||
self.settings["gps"] = [float(self.ui.lat_lineEdit.text()), float(self.ui.long_lineEdit.text())]
|
self.settings["gps"] = [
|
||||||
|
float(self.ui.lat_lineEdit.text()),
|
||||||
|
float(self.ui.long_lineEdit.text()),
|
||||||
|
]
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.settings["gps"] = "Wrong gps data"
|
self.settings["gps"] = "Wrong gps data"
|
||||||
else:
|
else:
|
||||||
|
|
@ -503,22 +530,47 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
self.settings["output_folder"] = self.get_text_value(self.ui.output_path)
|
self.settings["output_folder"] = self.get_text_value(self.ui.output_path)
|
||||||
self.settings["file_format"] = self.ui.image_type.currentText()
|
self.settings["file_format"] = self.ui.image_type.currentText()
|
||||||
# Quality
|
# Quality
|
||||||
self.settings["jpg_quality"] = self.get_spinbox_value(self.ui.jpg_quality_spinBox)
|
self.settings["jpg_quality"] = self.get_spinbox_value(
|
||||||
self.settings["png_compression"] = self.get_spinbox_value(self.ui.png_quality_spinBox)
|
self.ui.jpg_quality_spinBox
|
||||||
self.settings["resize"] = int(self.ui.resize_spinBox.text()) if self.ui.resize_spinBox.text() != "100" else None
|
)
|
||||||
|
self.settings["png_compression"] = self.get_spinbox_value(
|
||||||
|
self.ui.png_quality_spinBox
|
||||||
|
)
|
||||||
|
self.settings["resize"] = (
|
||||||
|
int(self.ui.resize_spinBox.text())
|
||||||
|
if self.ui.resize_spinBox.text() != "100"
|
||||||
|
else None
|
||||||
|
)
|
||||||
self.settings["optimize"] = self.get_checkbox_value(self.ui.optimize_checkBox)
|
self.settings["optimize"] = self.get_checkbox_value(self.ui.optimize_checkBox)
|
||||||
# Changes for image
|
# Changes for image
|
||||||
self.settings["brightness"] = int(self.ui.brightness_spinBox.text()) if self.ui.brightness_spinBox.text() != "0" else None
|
self.settings["brightness"] = (
|
||||||
self.settings["contrast"] = int(self.ui.contrast_spinBox.text()) if self.ui.contrast_spinBox.text() != "0" else None
|
int(self.ui.brightness_spinBox.text())
|
||||||
|
if self.ui.brightness_spinBox.text() != "0"
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
self.settings["contrast"] = (
|
||||||
|
int(self.ui.contrast_spinBox.text())
|
||||||
|
if self.ui.contrast_spinBox.text() != "0"
|
||||||
|
else None
|
||||||
|
)
|
||||||
self.settings["grayscale"] = self.get_checkbox_value(self.ui.grayscale_checkBox)
|
self.settings["grayscale"] = self.get_checkbox_value(self.ui.grayscale_checkBox)
|
||||||
# Watermark
|
# Watermark
|
||||||
self.settings["font_size"] = self.get_combobox_value(self.ui.font_size_comboBox)
|
self.settings["font_size"] = self.get_combobox_value(self.ui.font_size_comboBox)
|
||||||
self.settings["watermark"] = self.get_text_value(self.ui.watermark_lineEdit)
|
self.settings["watermark"] = self.get_text_value(self.ui.watermark_lineEdit)
|
||||||
# Naming
|
# Naming
|
||||||
new_name = self.get_text_value(self.ui.filename, False) if self.ui.rename_checkbox.isChecked() else False
|
new_name = (
|
||||||
if isinstance(new_name, str): new_name = new_name.replace(" ", "_")
|
self.get_text_value(self.ui.filename, False)
|
||||||
|
if self.ui.rename_checkbox.isChecked()
|
||||||
|
else False
|
||||||
|
)
|
||||||
|
if isinstance(new_name, str):
|
||||||
|
new_name = new_name.replace(" ", "_")
|
||||||
self.settings["new_file_names"] = new_name
|
self.settings["new_file_names"] = new_name
|
||||||
self.settings["invert_image_order"] = self.get_checkbox_value(self.ui.revert_checkbox) if new_name is not False else None
|
self.settings["invert_image_order"] = (
|
||||||
|
self.get_checkbox_value(self.ui.revert_checkbox)
|
||||||
|
if new_name is not False
|
||||||
|
else None
|
||||||
|
)
|
||||||
# Handle EXIF data selection
|
# Handle EXIF data selection
|
||||||
self.settings["copy_exif"] = self.get_checkbox_value(self.ui.exif_copy_checkBox)
|
self.settings["copy_exif"] = self.get_checkbox_value(self.ui.exif_copy_checkBox)
|
||||||
self.settings["own_exif"] = self.get_checkbox_value(self.ui.exif_checkbox)
|
self.settings["own_exif"] = self.get_checkbox_value(self.ui.exif_checkbox)
|
||||||
|
|
@ -550,11 +602,13 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
||||||
QApplication.closeAllWindows()
|
QApplication.closeAllWindows()
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
|
|
||||||
class WorkerSignals(QObject):
|
class WorkerSignals(QObject):
|
||||||
# ChatGPT
|
# ChatGPT
|
||||||
progress = Signal(int)
|
progress = Signal(int)
|
||||||
finished = Signal()
|
finished = Signal()
|
||||||
|
|
||||||
|
|
||||||
class ImageProcessorRunnable(QRunnable):
|
class ImageProcessorRunnable(QRunnable):
|
||||||
# ChatGPT gave rough function layout
|
# ChatGPT gave rough function layout
|
||||||
def __init__(self, image_files, settings, progress_callback):
|
def __init__(self, image_files, settings, progress_callback):
|
||||||
|
|
@ -573,27 +627,32 @@ class ImageProcessorRunnable(QRunnable):
|
||||||
for i, image_file in enumerate(self.image_files, start=1):
|
for i, image_file in enumerate(self.image_files, start=1):
|
||||||
input_path = os.path.join(input_folder, image_file)
|
input_path = os.path.join(input_folder, image_file)
|
||||||
if self.settings["new_file_names"] != False:
|
if self.settings["new_file_names"] != False:
|
||||||
image_name = self.u.append_number_to_name(self.settings["new_file_names"], i, len(self.image_files), self.settings["invert_image_order"])
|
image_name = self.u.append_number_to_name(
|
||||||
|
self.settings["new_file_names"],
|
||||||
|
i,
|
||||||
|
len(self.image_files),
|
||||||
|
self.settings["invert_image_order"],
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
image_name = os.path.splitext(image_file)[0]
|
image_name = os.path.splitext(image_file)[0]
|
||||||
output_path = os.path.join(output_folder, image_name)
|
output_path = os.path.join(output_folder, image_name)
|
||||||
|
|
||||||
self.o.process_and_save_image(
|
self.o.process_and_save_image(
|
||||||
image_input_file = input_path,
|
image_input_file=input_path,
|
||||||
image_output_file = output_path,
|
image_output_file=output_path,
|
||||||
file_type = self.settings["file_format"],
|
file_type=self.settings["file_format"],
|
||||||
quality = self.settings["jpg_quality"],
|
quality=self.settings["jpg_quality"],
|
||||||
compressing = self.settings["png_compression"],
|
compressing=self.settings["png_compression"],
|
||||||
optimize = self.settings["optimize"],
|
optimize=self.settings["optimize"],
|
||||||
resize = self.settings["resize"],
|
resize=self.settings["resize"],
|
||||||
watermark = self.settings["watermark"],
|
watermark=self.settings["watermark"],
|
||||||
font_size = self.settings["font_size"],
|
font_size=self.settings["font_size"],
|
||||||
grayscale = self.settings["grayscale"],
|
grayscale=self.settings["grayscale"],
|
||||||
brightness = self.settings["brightness"],
|
brightness=self.settings["brightness"],
|
||||||
contrast = self.settings["contrast"],
|
contrast=self.settings["contrast"],
|
||||||
dict_for_exif = self.settings["user_selected_exif"],
|
dict_for_exif=self.settings["user_selected_exif"],
|
||||||
gps = self.settings["gps"],
|
gps=self.settings["gps"],
|
||||||
copy_exif = self.settings["copy_exif"]
|
copy_exif=self.settings["copy_exif"],
|
||||||
)
|
)
|
||||||
self.signals.progress.emit(int((i / len(self.image_files)) * 100))
|
self.signals.progress.emit(int((i / len(self.image_files)) * 100))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,20 @@
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from PyPiUpdater import PyPiUpdater
|
from PyPiUpdater import PyPiUpdater
|
||||||
|
from PySide6 import QtCore, QtWidgets
|
||||||
|
from PySide6.QtCore import QRegularExpression, Qt, QTimer
|
||||||
|
from PySide6.QtGui import QIcon
|
||||||
|
from PySide6.QtWidgets import QApplication, QMainWindow, QMessageBox
|
||||||
|
|
||||||
from OptimaLab35 import __version__
|
from OptimaLab35 import __version__
|
||||||
from .const import (
|
|
||||||
CONFIG_BASE_PATH
|
|
||||||
)
|
|
||||||
|
|
||||||
|
from .const import CONFIG_BASE_PATH
|
||||||
from .ui import resources_rc
|
from .ui import resources_rc
|
||||||
from .utils.utility import Utilities
|
|
||||||
from .ui.settings_window import Ui_Settings_Window
|
from .ui.settings_window import Ui_Settings_Window
|
||||||
|
from .utils.utility import Utilities
|
||||||
|
|
||||||
from PySide6 import QtWidgets, QtCore
|
|
||||||
|
|
||||||
from PySide6.QtCore import (
|
|
||||||
QRegularExpression,
|
|
||||||
Qt,
|
|
||||||
QTimer
|
|
||||||
)
|
|
||||||
|
|
||||||
from PySide6.QtWidgets import (
|
|
||||||
QMessageBox,
|
|
||||||
QApplication,
|
|
||||||
QMainWindow
|
|
||||||
)
|
|
||||||
|
|
||||||
from PySide6.QtGui import QIcon
|
|
||||||
|
|
||||||
class SettingsWindow(QMainWindow, Ui_Settings_Window):
|
class SettingsWindow(QMainWindow, Ui_Settings_Window):
|
||||||
# Mixture of code by me, code/functions refactored by ChatGPT and code directly from ChatGPT
|
# Mixture of code by me, code/functions refactored by ChatGPT and code directly from ChatGPT
|
||||||
|
|
@ -45,8 +33,12 @@ class SettingsWindow(QMainWindow, Ui_Settings_Window):
|
||||||
self.optimalab35_localversion = optimalab35_localversion
|
self.optimalab35_localversion = optimalab35_localversion
|
||||||
self.optima35_localversion = optima35_localversion
|
self.optima35_localversion = optima35_localversion
|
||||||
# Create PyPiUpdater instances
|
# Create PyPiUpdater instances
|
||||||
self.ppu_ol35 = PyPiUpdater("OptimaLab35", self.optimalab35_localversion, self.update_log_file)
|
self.ppu_ol35 = PyPiUpdater(
|
||||||
self.ppu_o35 = PyPiUpdater("optima35", self.optima35_localversion, self.update_log_file)
|
"OptimaLab35", self.optimalab35_localversion, self.update_log_file
|
||||||
|
)
|
||||||
|
self.ppu_o35 = PyPiUpdater(
|
||||||
|
"optima35", self.optima35_localversion, self.update_log_file
|
||||||
|
)
|
||||||
self.ol35_last_state = self.ppu_ol35.get_last_state()
|
self.ol35_last_state = self.ppu_ol35.get_last_state()
|
||||||
self.o35_last_state = self.ppu_o35.get_last_state()
|
self.o35_last_state = self.ppu_o35.get_last_state()
|
||||||
# Track which packages need an update
|
# Track which packages need an update
|
||||||
|
|
@ -68,7 +60,9 @@ class SettingsWindow(QMainWindow, Ui_Settings_Window):
|
||||||
# Connect buttons to functions
|
# Connect buttons to functions
|
||||||
self.ui.check_for_update_Button.clicked.connect(self.check_for_updates)
|
self.ui.check_for_update_Button.clicked.connect(self.check_for_updates)
|
||||||
self.ui.update_and_restart_Button.clicked.connect(self.update_and_restart)
|
self.ui.update_and_restart_Button.clicked.connect(self.update_and_restart)
|
||||||
self.ui.label_last_check.setText(f"Last check: {self.time_to_string(self.ol35_last_state[0])}")
|
self.ui.label_last_check.setText(
|
||||||
|
f"Last check: {self.time_to_string(self.ol35_last_state[0])}"
|
||||||
|
)
|
||||||
self.ui.dev_widget.setVisible(False)
|
self.ui.dev_widget.setVisible(False)
|
||||||
|
|
||||||
# Timer for long press detection
|
# Timer for long press detection
|
||||||
|
|
@ -79,20 +73,22 @@ class SettingsWindow(QMainWindow, Ui_Settings_Window):
|
||||||
# Connect button press/release
|
# Connect button press/release
|
||||||
self.ui.check_for_update_Button.pressed.connect(self.start_long_press)
|
self.ui.check_for_update_Button.pressed.connect(self.start_long_press)
|
||||||
self.ui.check_for_update_Button.released.connect(self.cancel_long_press)
|
self.ui.check_for_update_Button.released.connect(self.cancel_long_press)
|
||||||
self.ui.label_5.setText('<li><a href="https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/src/branch/main/CHANGELOG.md">Changelog</a></li>')
|
self.ui.label_5.setText(
|
||||||
|
'<li><a href="https://code.boxyfoxy.net/CodeByMrFinchum/OptimaLab35/wiki/Changelog">Changelog</a></li>'
|
||||||
|
)
|
||||||
self.ui.label_5.setOpenExternalLinks(True)
|
self.ui.label_5.setOpenExternalLinks(True)
|
||||||
#settings related
|
# settings related
|
||||||
self.load_settings_into_ui()
|
self.load_settings_into_ui()
|
||||||
self.ui.reset_exif_Button.clicked.connect(self.ask_reset_exif)
|
self.ui.reset_exif_Button.clicked.connect(self.ask_reset_exif)
|
||||||
self.ui.save_and_close_Button.clicked.connect(self.save_and_close)
|
self.ui.save_and_close_Button.clicked.connect(self.save_and_close)
|
||||||
self.ui.save_and_restart_Button.clicked.connect(self.save_and_restart)
|
self.ui.save_and_restart_Button.clicked.connect(self.save_and_restart)
|
||||||
|
|
||||||
if os.name == "nt": # Disable restart app when windows.
|
if os.name == "nt": # Disable restart app when windows.
|
||||||
self.ui.save_and_restart_Button.setVisible(False)
|
self.ui.save_and_restart_Button.setVisible(False)
|
||||||
self.ui.restart_checkBox.setChecked(False)
|
self.ui.restart_checkBox.setChecked(False)
|
||||||
self.ui.restart_checkBox.setVisible(False)
|
self.ui.restart_checkBox.setVisible(False)
|
||||||
|
|
||||||
# setting related
|
# setting related
|
||||||
def load_settings_into_ui(self):
|
def load_settings_into_ui(self):
|
||||||
"""Loads the settings into the UI elements."""
|
"""Loads the settings into the UI elements."""
|
||||||
settings = self.app_settings
|
settings = self.app_settings
|
||||||
|
|
@ -101,7 +97,9 @@ class SettingsWindow(QMainWindow, Ui_Settings_Window):
|
||||||
pkg_available = settings["theme"]["theme_pkg"]
|
pkg_available = settings["theme"]["theme_pkg"]
|
||||||
|
|
||||||
if pkg_available:
|
if pkg_available:
|
||||||
index = self.ui.theme_selection_comboBox.findText(theme_mode, QtCore.Qt.MatchFlag.MatchExactly)
|
index = self.ui.theme_selection_comboBox.findText(
|
||||||
|
theme_mode, QtCore.Qt.MatchFlag.MatchExactly
|
||||||
|
)
|
||||||
if index != -1:
|
if index != -1:
|
||||||
self.ui.theme_selection_comboBox.setCurrentIndex(index)
|
self.ui.theme_selection_comboBox.setCurrentIndex(index)
|
||||||
self.ui.enable_theme_checkBox.setChecked(use_custom_theme)
|
self.ui.enable_theme_checkBox.setChecked(use_custom_theme)
|
||||||
|
|
@ -130,8 +128,12 @@ class SettingsWindow(QMainWindow, Ui_Settings_Window):
|
||||||
self.ui.install_pkg_Button.setText("Try again?")
|
self.ui.install_pkg_Button.setText("Try again?")
|
||||||
|
|
||||||
def save_settings(self):
|
def save_settings(self):
|
||||||
self.app_settings["theme"]["mode"] = self.ui.theme_selection_comboBox.currentText()
|
self.app_settings["theme"]["mode"] = (
|
||||||
self.app_settings["theme"]["use_custom_theme"] = self.ui.enable_theme_checkBox.isChecked()
|
self.ui.theme_selection_comboBox.currentText()
|
||||||
|
)
|
||||||
|
self.app_settings["theme"]["use_custom_theme"] = (
|
||||||
|
self.ui.enable_theme_checkBox.isChecked()
|
||||||
|
)
|
||||||
self.u.save_settings(self.app_settings)
|
self.u.save_settings(self.app_settings)
|
||||||
|
|
||||||
def save_and_close(self):
|
def save_and_close(self):
|
||||||
|
|
@ -143,7 +145,9 @@ class SettingsWindow(QMainWindow, Ui_Settings_Window):
|
||||||
msg.setIcon(QMessageBox.Icon.Question)
|
msg.setIcon(QMessageBox.Icon.Question)
|
||||||
msg.setWindowTitle("Confirm Reset")
|
msg.setWindowTitle("Confirm Reset")
|
||||||
msg.setText("Are you sure you want to restart the app?")
|
msg.setText("Are you sure you want to restart the app?")
|
||||||
msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
msg.setStandardButtons(
|
||||||
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||||
|
)
|
||||||
|
|
||||||
# Show the message box and wait for the user's response
|
# Show the message box and wait for the user's response
|
||||||
response = msg.exec()
|
response = msg.exec()
|
||||||
|
|
@ -162,7 +166,9 @@ class SettingsWindow(QMainWindow, Ui_Settings_Window):
|
||||||
msg.setIcon(QMessageBox.Icon.Question)
|
msg.setIcon(QMessageBox.Icon.Question)
|
||||||
msg.setWindowTitle("Confirm Reset")
|
msg.setWindowTitle("Confirm Reset")
|
||||||
msg.setText("Are you sure you want to reset the EXIF options to default?")
|
msg.setText("Are you sure you want to reset the EXIF options to default?")
|
||||||
msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
msg.setStandardButtons(
|
||||||
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||||
|
)
|
||||||
|
|
||||||
# Show the message box and wait for the user's response
|
# Show the message box and wait for the user's response
|
||||||
response = msg.exec()
|
response = msg.exec()
|
||||||
|
|
@ -173,7 +179,7 @@ class SettingsWindow(QMainWindow, Ui_Settings_Window):
|
||||||
else:
|
else:
|
||||||
pass # Do nothing if "No" is selected
|
pass # Do nothing if "No" is selected
|
||||||
|
|
||||||
# update related parts
|
# update related parts
|
||||||
def start_long_press(self):
|
def start_long_press(self):
|
||||||
"""Start the timer when button is pressed."""
|
"""Start the timer when button is pressed."""
|
||||||
# brave AI
|
# brave AI
|
||||||
|
|
@ -214,7 +220,9 @@ class SettingsWindow(QMainWindow, Ui_Settings_Window):
|
||||||
|
|
||||||
def local_update(self):
|
def local_update(self):
|
||||||
dist_folder = os.path.expanduser("~/.config/OptimaLab35/dist/")
|
dist_folder = os.path.expanduser("~/.config/OptimaLab35/dist/")
|
||||||
packages_to_update = [pkg for pkg, update in self.updates_available.items() if update]
|
packages_to_update = [
|
||||||
|
pkg for pkg, update in self.updates_available.items() if update
|
||||||
|
]
|
||||||
|
|
||||||
if not packages_to_update:
|
if not packages_to_update:
|
||||||
QMessageBox.information(self, "Update", "No updates available.")
|
QMessageBox.information(self, "Update", "No updates available.")
|
||||||
|
|
@ -243,7 +251,9 @@ class SettingsWindow(QMainWindow, Ui_Settings_Window):
|
||||||
elif package == "optima35":
|
elif package == "optima35":
|
||||||
pkg_info = self.ppu_o35.update_from_local(dist_folder)
|
pkg_info = self.ppu_o35.update_from_local(dist_folder)
|
||||||
|
|
||||||
update_results.append(f"{package}: {'Success' if pkg_info[0] else 'Failed'}\n{pkg_info[1]}")
|
update_results.append(
|
||||||
|
f"{package}: {'Success' if pkg_info[0] else 'Failed'}\n{pkg_info[1]}"
|
||||||
|
)
|
||||||
|
|
||||||
# Show summary of updates
|
# Show summary of updates
|
||||||
# Show update completion message
|
# Show update completion message
|
||||||
|
|
@ -302,7 +312,9 @@ class SettingsWindow(QMainWindow, Ui_Settings_Window):
|
||||||
|
|
||||||
def update_and_restart(self):
|
def update_and_restart(self):
|
||||||
"""Update selected packages and restart the application."""
|
"""Update selected packages and restart the application."""
|
||||||
packages_to_update = [pkg for pkg, update in self.updates_available.items() if update]
|
packages_to_update = [
|
||||||
|
pkg for pkg, update in self.updates_available.items() if update
|
||||||
|
]
|
||||||
|
|
||||||
if not packages_to_update:
|
if not packages_to_update:
|
||||||
QMessageBox.information(self, "Update", "No updates available.")
|
QMessageBox.information(self, "Update", "No updates available.")
|
||||||
|
|
@ -331,7 +343,9 @@ class SettingsWindow(QMainWindow, Ui_Settings_Window):
|
||||||
elif package == "optima35":
|
elif package == "optima35":
|
||||||
pkg_info = self.ppu_o35.update_package()
|
pkg_info = self.ppu_o35.update_package()
|
||||||
|
|
||||||
update_results.append(f"{package}: {'Success' if pkg_info[0] else 'Failed'}\n{pkg_info[1]}")
|
update_results.append(
|
||||||
|
f"{package}: {'Success' if pkg_info[0] else 'Failed'}\n{pkg_info[1]}"
|
||||||
|
)
|
||||||
|
|
||||||
# Show summary of updates
|
# Show summary of updates
|
||||||
# Show update completion message
|
# Show update completion message
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ dynamic = ["version"]
|
||||||
authors = [{ name = "Mr Finchum" }]
|
authors = [{ name = "Mr Finchum" }]
|
||||||
description = "User interface for optima35."
|
description = "User interface for optima35."
|
||||||
readme = "../pip_README.md"
|
readme = "../pip_README.md"
|
||||||
requires-python = ">=3.8, <4.0"
|
requires-python = ">=3.9, <3.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"optima35>=1.0.0, <2.0.0",
|
"optima35>=1.0.0, <2.0.0",
|
||||||
"PyPiUpdater>=0.7.2, <1.0.0",
|
"PyPiUpdater>=0.7.2, <1.0.0",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue