From dc327403099ab03eabbf5f11155a0955055fe0dd Mon Sep 17 00:00:00 2001 From: Mr Finchum Date: Tue, 28 Jan 2025 15:06:29 +0000 Subject: [PATCH 1/8] refactor!: Stable release for optima35 --- .gitlab-ci.yml | 2 +- CHANGELOG.md | 23 ++++ README.md | 68 +++++----- src/optima35/__init__.py | 2 +- src/optima35/core.py | 247 +++++++++++++++++++++++----------- src/optima35/image_handler.py | 11 +- 6 files changed, 232 insertions(+), 121 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aa405f9..30a2cc5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,7 +26,7 @@ build: - job: gitversion artifacts: true script: - - sed -i "s/0.0.1/${GitVersion_MajorMinorPatch}/" src/optima35/__init__.py + - sed -i "s/1.0.0/${GitVersion_MajorMinorPatch}/" src/optima35/__init__.py - cat src/optima35/__init__.py - python3 -m pip install build - python3 -m build diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b241c9..ee5de51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 1.0.0: +### Refactoring +- Added function descriptions for better clarity and maintainability. +- Introduced guidelines for each function, defining objectives and expected behavior. + +--- + ## 0.12.x ### 0.12.2: Bug fixes - Fixed missing lens in meta data @@ -11,11 +18,15 @@ ### 0.12.0 - Versioning from pipeline. +--- + ## 0.7.x ### 0.7.0 - **BREAKING CHANGE:** GPS location must now be provided as a float instead of a string. - Repo only: Pipline +--- + ## 0.6.x ### 0.6.8 - Repo only: Pipline @@ -46,6 +57,8 @@ - Working on to Publish on pypi - Renaming of files and classes +--- + ## 0.5.x ### 0.5.0 ### **OPTIMA35 0.5.0: Code Cleaning and Preparation for Split** @@ -57,6 +70,8 @@ - Updated **GUI** and **TUI** to work seamlessly with the new **OPTIMA35** class. - Ensured compatibility with the newly organized codebase in the OPTIMA35 package. +--- + ## 0.4.x ### 0.4.1: Finished GUI and TUI - Both **GUI** and **TUI** now fully utilize the `optima35` class for core functionality. @@ -82,6 +97,8 @@ - Improved readability, maintainability, and scalability of the project. - Easier to test and debug individual components. +--- + ## 0.3.x ### 0.3.4: Features Finalized - Core Features Completed: @@ -128,6 +145,8 @@ - Watermark is still in testing / alpha - Original TUI version was forked and is still aviable, currently this branch includes the TUI version until the next minor version change. +--- + ## 0.2.x ### 0.2.1: Merge from TUI fork - Ensure watermark is white with black borders. @@ -136,6 +155,8 @@ - **Cleaner folder structure** - Moving files with classes to different folder to keep project cleaner. +--- + ## 0.1.x ### 0.1.1 - **Add Original to add Timestamp to Images** @@ -165,6 +186,8 @@ - At the start of the program, the user is asked to save default values, such as JPG quality, resize options, and more. This way, the settings don't have to be entered at every start. Upon starting, the user is prompted to confirm whether they want to keep the current settings from the settings file. - Options for changing EXIF data are saved in exif_options.yaml. Here, you can enter all the models, lenses, etc., you would like to select within the program. +--- + ## 0.0.x ### 0.0.3: Enhanced Functionality - now useable - **New Image Modification Functions:** diff --git a/README.md b/README.md index 87cd456..1936720 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,55 @@ # **OPTIMA35** -[optima35](https://gitlab.com/CodeByMrFinchum/optima35) is a Python package for managing and editing images, with a focus on analog photography (using pillow and piexif). For a graphical user interface, see [OptimaLab35](https://gitlab.com/CodeByMrFinchum/OptimaLab35). +**OPTIMA35** stands for **Organizing, Processing, Tweaking Images, and Modifying Analogs from 35mm Film**. It is a Python package designed to simplify image editing and metadata management, providing an interface/API for handling image and EXIF data seamlessly. While OPTIMA35 was created with analog photography in mind—where scanned images often lack proper EXIF data or retain only scanner metadata—it is equally useful for digital images. Adding or managing EXIF data is invaluable for organizing private photo collections, making your photo library more structured and searchable. -## **Installation** -Install with pip (dependencies will be installed automatically): -```bash -pip install optima35 -``` -and the GUI with -```bash -pip install OptimaLab35 -``` +OPTIMA35 is a core package that acts as an interface for libraries like Pillow and piexif, simplifying image manipulation tasks. While it modifies images one at a time, it requires a dedicated program for flexible and batch processing. For a user-friendly graphical experience, consider using [OptimaLab35](https://gitlab.com/CodeByMrFinchum/OptimaLab35), a GUI designed specifically for OPTIMA35, also developed by me. -## **Overview** +Currently, there are no plans to create a formal API documentation. The code includes annotations and detailed function descriptions to explain its functionality. As this is a private hobby project, dedicating time to writing comprehensive documentation would take away from my limited free time. -**OPTIMA35** (**Organizing, Processing, Tweaking Images, and Modifying scanned Analogs from 35mm Film**) simplifies the editing and management of images and metadata. Though optimized for analog photography, it can handle any type of images. +--- ## **Features** ### **Image Processing** -- Resize images -- Rename with custom order -- Grayscale conversion -- Brightness and contrast adjustment +- Resize images (upscale or downscale) +- Convert images to grayscale +- Adjust brightness and contrast +- Add customizable text-based watermarks ### **EXIF Management** -- Copy or add custom EXIF data -- Add GPS coordinates -- Add or modify EXIF dates -- Remove EXIF metadata +- Add EXIF data using a simple dictionary +- Copy EXIF data from the original image +- Remove EXIF metadata completely +- Add timestamps (e.g., original photo timestamp) +- Automatically adjust EXIF timestamps based on image file names +- Add GPS coordinates to images -### **Watermarking** -- Add customizable watermarks to images +### **Streamlined Integration** +- Handles all required EXIF byte conversions behind the scenes +- Provides an intuitive API for frequently needed operations + +--- + +## **Installation** +Install the GUI (dependencies are installed automatically) +```bash +pip install OptimaLab35 +``` + +Or in case you only want optima35 (dependencies are installed automatically): +```bash +pip install optima35 +``` + +--- ## **Current Status** +**Stable Release (v1.0)** +- The program follows semantic versioning (**major.minor.patch**). +- The current release is stable, and all changes within the same major version will remain backward compatible. +- Breaking changes, if any, will result in a new major version. +- Future development will primarily focus on the graphical user interface (OptimaLab35), with only minor updates or patches for OPTIMA35 as needed. -**Alpha Stage** -- Active development with frequent updates. -- Breaking changes may occur in minor version updates. -- Check the [CHANGELOG](https://gitlab.com/CodeByMrFinchum/optima35/-/blob/main/CHANGELOG.md?ref_type=heads) for details on changes and updates. - -## **Contributing and Feedback** - -Feedback, bug reports, and contributions are welcome! Please submit them through the [GitLab repository](https://gitlab.com/CodeByMrFinchum/optima35). +--- # Use of LLMs In the interest of transparency, I disclose that Generative AI (GAI) large language models (LLMs), including OpenAI’s ChatGPT and Ollama models (e.g., OpenCoder and Qwen2.5-coder), have been used to assist in this project. diff --git a/src/optima35/__init__.py b/src/optima35/__init__.py index f102a9c..5becc17 100644 --- a/src/optima35/__init__.py +++ b/src/optima35/__init__.py @@ -1 +1 @@ -__version__ = "0.0.1" +__version__ = "1.0.0" diff --git a/src/optima35/core.py b/src/optima35/core.py index 9ff8a53..088f346 100644 --- a/src/optima35/core.py +++ b/src/optima35/core.py @@ -11,7 +11,7 @@ class OptimaManager: self.image_processor = ImageProcessor() self.exif_handler = ExifHandler() - def modify_timestamp_in_exif(self, data_for_exif: dict, filename: str): + def _modify_timestamp_in_exif(self, data_for_exif: dict, filename: str): """"Takes a dict formated for exif use by piexif and adjusts the date_time_original, changing the minutes and seconds to fit the number of the filname.""" last_three = filename[-3:len(filename)] total_seconds = int(re.sub(r'\D+', '', last_three)) @@ -22,101 +22,184 @@ class OptimaManager: data_for_exif["date_time_original"] = new_time.strftime("%Y:%m:%d %H:%M:%S") return data_for_exif - def process_image(self, # TODO: split into two classes, one for modification for one saving.. - image_input_file, - image_output_file, - file_type = "jpg", - quality = 90, - compressing = 6, - optimize = False, - resize = None, - watermark = None, - font_size = 2, - grayscale = False, - brightness = None, - contrast = None, - dict_for_exif = None, - gps = None, - copy_exif = False, - save = True): - # Partly optimized by ChatGPT - # Open the image file - with self.image_processor.open_image(image_input_file) as img: - processed_img = img - image_name = os.path.basename(image_output_file) # for date adjustment - # Resize - if resize is not None: - processed_img = self.image_processor.resize_image( - image=processed_img, percent = resize + def _process_image( + self, + image_input_file: str, + resize: int = None, + watermark: str = None, + font_size: int = 2, + grayscale: bool = False, + brightness: float = None, + contrast: float = None + ): + # My Code restructured by ChatGPT, but had to fix bugs + img = self.image_processor.open_image(image_input_file) + # Apply transformations + if resize is not None: + img = self.image_processor.resize_image(img, percent=resize) + if watermark is not None: + img = self.image_processor.add_watermark(img, watermark, font_size) + if grayscale: + img = self.image_processor.grayscale(img) + if brightness is not None: + img = self.image_processor.change_brightness(img, brightness) + if contrast is not None: + img = self.image_processor.change_contrast(img, contrast) + return img + + def _handle_exif( + self, + image, + file_name, + dict_for_exif: dict = None, + gps: tuple[float, float] = None, + copy_exif: bool = False + ): + # My Code restructured by ChatGPT, but had to fix bugs + # Build or copy EXIF data + if dict_for_exif: + if "date_time_original" in dict_for_exif: + dict_for_exif = self._modify_timestamp_in_exif(dict_for_exif, file_name) + exif_data = self.exif_handler.build_exif_bytes( + dict_for_exif, self.image_processor.get_image_size(image) + ) + if gps: + exif_data = self.exif_handler.add_geolocation_to_exif( + exif_data, gps[0], gps[1] ) + elif copy_exif: + exif_data = self.exif_handler.get_exif_info(image) + else: + exif_data = None + return exif_data - # Watermark - if watermark is not None: - processed_img = self.image_processor.add_watermark( - processed_img, watermark, int(font_size) - ) + def process_and_save_image( + self, + image_input_file: str, + image_output_file: str, + file_type: str = "jpg", + quality: int = 90, + compressing: int = 6, + optimize: bool = False, + resize: int = None, + watermark: str = None, + font_size: int = 2, + grayscale: bool = False, + brightness: float = None, + contrast: float = None, + dict_for_exif: dict = None, + gps: tuple[float, float] = None, + copy_exif: bool = False + ) -> None: + """ + Processes an image with the given parameters and saves the output to a file. - # Grayscale - if grayscale: - processed_img = self.image_processor.grayscale(processed_img) + Args: + image_input_file (str): Path to the input image file. + image_output_file (str): Path to save the processed image. + file_type (str): Output image format ('jpg', 'png'). Defaults to 'jpg'. + quality (int): JPEG quality (1-100). Defaults to 90. + compressing (int): PNG compression level (0-9). Defaults to 6. + optimize (bool): Optimize image for smaller file size. Defaults to False. + resize (int, optional): Resize percentage. Defaults to None. + watermark (str, optional): Watermark text to add. Defaults to None. + font_size (int): Font size for the watermark. Defaults to 2. + grayscale (bool): Convert image to grayscale. Defaults to False. + brightness (float, optional): Adjust brightness (e.g., 1.2 for 20% brighter). Defaults to None. + contrast (float, optional): Adjust contrast (e.g., 1.5 for 50% higher contrast). Defaults to None. + dict_for_exif (dict, optional): EXIF metadata to insert. Defaults to None. + gps (tuple[float, float], optional): GPS coordinates (latitude, longitude). Defaults to None. + copy_exif (bool): Copy EXIF metadata from the input image. Defaults to False. - # Brightness - if brightness is not None: - processed_img = self.image_processor.change_brightness( - processed_img, brightness - ) + Returns: + None + """ + # My Code restructured by ChatGPT + processed_img = self._process_image( + image_input_file, + resize, + watermark, + font_size, + grayscale, + brightness, + contrast, + ) - # Contrast - if contrast is not None: - processed_img = self.image_processor.change_contrast( - processed_img, contrast - ) + # Handle EXIF metadata + exif_piexif_format = self._handle_exif( + image = processed_img, + file_name = image_output_file, + dict_for_exif = dict_for_exif, + gps = gps, + copy_exif = copy_exif + ) - # EXIF data handling - exif_piexif_format = None - if dict_for_exif: # todo: maybe move to ui and only accept complete exif dicts.. - selected_exif = dict_for_exif - if "date_time_original" in dict_for_exif: - selected_exif = self.modify_timestamp_in_exif(selected_exif, image_name) - exif_piexif_format = self.exif_handler.build_exif_bytes( - selected_exif, self.image_processor.get_image_size(processed_img) - ) + # Save the image + self.image_processor.save_image( + image = processed_img, + path = image_output_file, + piexif_exif_data = exif_piexif_format, + file_type = file_type, + jpg_quality = quality, + png_compressing = compressing, + optimize = optimize, + ) - # GPS data - if gps is not None: - latitude = float(gps[0]) - longitude = float(gps[1]) - exif_piexif_format = self.exif_handler.add_geolocation_to_exif(exif_piexif_format, latitude, longitude) + def process_image_object( + self, + image_input_file: str, + resize: int = None, + watermark: str = None, + font_size: int = 2, + grayscale: bool = False, + brightness: float = None, + contrast: float = None + ): + """ + Processes an image with the given parameters and returns the modified image object. - # Copy EXIF data if selected, and ensure size is correct in exif data - elif copy_exif: - try: - og_exif = self.exif_handler.get_exif_info(img) - og_exif["Exif"][40962], og_exif["Exif"][40963] = self.image_processor.get_image_size(processed_img) - exif_piexif_format = og_exif - except Exception: - print("Copying EXIF data selected, but no EXIF data is available in the original image file.") + Args: + image_input_file (str): Path to the input image file. + resize (int, optional): Resize percentage. Defaults to None. + watermark (str, optional): Watermark text to add. Defaults to None. + font_size (int): Font size for the watermark. Defaults to 2. + grayscale (bool): Convert image to grayscale. Defaults to False. + brightness (float, optional): Adjust brightness. Defaults to None. + contrast (float, optional): Adjust contrast. Defaults to None. - if save: - # Save the processed image - self.image_processor.save_image( - image = processed_img, - path = image_output_file, - piexif_exif_data = exif_piexif_format, - file_type = file_type, - jpg_quality = quality, - png_compressing = compressing, - optimize = optimize - ) - else: - return self.image_processor.convert_pil_to_qtimage(processed_img) + Returns: + Image: The processed image object. + """ + # My Code restructured by ChatGPT + processed_img = self._process_image( + image_input_file, + resize, + watermark, + font_size, + grayscale, + brightness, + contrast, + ) + return self.image_processor.convert_pil_to_qtimage(processed_img) - def insert_dict_to_image(self, exif_dict, image_path, gps = None): + def insert_exif_to_image(self, exif_dict: dict, image_path: str, gps: tuple[float, float] = None) -> None: + """ + Inserts EXIF metadata into an image. + + Args: + exif_data (dict): A dictionary containing EXIF metadata as key-value pairs (e.g., strings, integers). + image_path (str): Absolute path to the target image file. + gps (tuple[float, float], optional): GPS coordinates as a tuple (latitude, longitude). Defaults to None. + + Returns: + None: The function modifies the image file in place. + """ + # Restructured by ChatGPT image_name, ending = os.path.splitext(os.path.basename(image_path)) img = self.image_processor.open_image(image_path) selected_exif = exif_dict if "date_time_original" in exif_dict: - selected_exif = self.modify_timestamp_in_exif(selected_exif, image_name) + selected_exif = self._modify_timestamp_in_exif(selected_exif, image_name) exif_piexif_format = self.exif_handler.build_exif_bytes( selected_exif, self.image_processor.get_image_size(img) diff --git a/src/optima35/image_handler.py b/src/optima35/image_handler.py index 0bf8c1a..590cede 100644 --- a/src/optima35/image_handler.py +++ b/src/optima35/image_handler.py @@ -35,18 +35,15 @@ class ImageProcessor: def resize_image(self, image, percent, resample = True): """Resize an image by giving a percent.""" new_size = tuple(int(x * (percent / 100)) for x in image.size) - if resample: - resized_image = image.resize(new_size) - else: - resized_image = image.resize((new_size),resample=Image.Resampling.NEAREST) + resized_image = image.resize(new_size) return resized_image def add_watermark(self, image, text, font_size_percentage): """Addes a watermark to the image using default os font.""" drawer = ImageDraw.Draw(image) imagewidth, imageheight = image.size - margin = (imageheight / 100 ) * 2 # margin dynamic, 2% of image size - font_size = imagewidth * (font_size_percentage / 100) + margin = (imageheight / 100) * 2 # margin dynamic, 2% of image size + font_size = (imagewidth / 100) * font_size_percentage try: # Try loading front, if notaviable return unmodified image font = ImageFont.load_default(font_size) @@ -80,7 +77,7 @@ class ImageProcessor: elif file_type == "png": save_params["compress_level"] = png_compressing elif file_type not in ["webp", "jpg", "png"]: - input(f"Type: {file_type} is not supported. Press Enter to continue...") + print(f"Type: {file_type} is not supported.") return # Add EXIF data if available if piexif_exif_data is not None: From 4f7fc53c92775720d7d1aef9ad3ec7ad5fc15a33 Mon Sep 17 00:00:00 2001 From: Mr Finchum Date: Tue, 28 Jan 2025 15:29:02 +0000 Subject: [PATCH 2/8] patch: Adding contribution section to readme --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1936720..ebd7622 100644 --- a/README.md +++ b/README.md @@ -50,11 +50,14 @@ pip install optima35 - Future development will primarily focus on the graphical user interface (OptimaLab35), with only minor updates or patches for OPTIMA35 as needed. --- +# Contribution -# Use of LLMs +Thanks to developer [Mr Finch](https://gitlab.com/MrFinchMkV) for contributing to this project, and for initiating and helping setting up the CI/CD pipeline. + +## Use of LLMs In the interest of transparency, I disclose that Generative AI (GAI) large language models (LLMs), including OpenAI’s ChatGPT and Ollama models (e.g., OpenCoder and Qwen2.5-coder), have been used to assist in this project. -## Areas of Assistance: +### Areas of Assistance: - Project discussions and planning - Spelling and grammar corrections - Suggestions for suitable packages and libraries @@ -66,7 +69,7 @@ In cases where LLMs contribute directly to code or provide substantial optimizat - mradermacher gguf Q4K-M Instruct version of infly/OpenCoder-1.5B - unsloth gguf Q4K_M Instruct version of both Qwen/QWEN2 1.5B and 3B -### References +#### References 1. **Huang, Siming, et al.** *OpenCoder: The Open Cookbook for Top-Tier Code Large Language Models.* 2024. [PDF](https://arxiv.org/pdf/2411.04905) From 3233b92fba4afa9fb84f9ea0a3541715ebe73b20 Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Sun, 9 Feb 2025 13:21:29 +0100 Subject: [PATCH 3/8] patch: Added classifiers for pypi --- CHANGELOG.md | 11 +++++++++-- pyproject.toml | 5 +++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee5de51..d1d80e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,14 @@ # Changelog -## 1.0.0: -### Refactoring +## 1.0.x + +### 1.0.2: Added Classifier +- Added classifiers in `pyproject.toml` for PyPI. + +### 1.0.1: Contribution +- Added a contribution section in the README for Mr. Finch. + +### **1.0.0** Refactoring - Added function descriptions for better clarity and maintainability. - Introduced guidelines for each function, defining objectives and expected behavior. diff --git a/pyproject.toml b/pyproject.toml index 1ee9c4e..85776ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,8 @@ readme = "pip_README.md" requires-python = ">=3.8" dependencies = ["piexif", "pillow"] classifiers = [ + "Development Status :: 5 - Production/Stable", + "Topic :: Software Development :: Libraries :: Python Modules", "Programming Language :: Python :: 3", "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", "Operating System :: OS Independent", @@ -18,6 +20,9 @@ classifiers = [ [project.urls] Source = "https://gitlab.com/CodeByMrFinchum/optima35" +Documentation = "https://gitlab.com/CodeByMrFinchum/optima35/-/blob/main/README.md" +Changelog = "https://gitlab.com/CodeByMrFinchum/optima35/-/blob/main/CHANGELOG.md" + [project.scripts] optima35 = "optima35.__main__:main" From e2cef24b658af361bc505a90a143fa5701eba70b Mon Sep 17 00:00:00 2001 From: "Mr. Finchum" Date: Mon, 24 Mar 2025 15:24:14 +0100 Subject: [PATCH 4/8] patch: adjusted pyproject.toml --- CHANGELOG.md | 8 +++++--- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1d80e2..a4ceb7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,16 @@ # Changelog ## 1.0.x +### 1.0.3 Patch: Adjusted pyproject.toml (25.03.24) +- Added <4.0 python version -### 1.0.2: Added Classifier +### 1.0.2: Added Classifier (25.02.09) - Added classifiers in `pyproject.toml` for PyPI. -### 1.0.1: Contribution +### 1.0.1: Contribution (25.01.28) - Added a contribution section in the README for Mr. Finch. -### **1.0.0** Refactoring +### **1.0.0** Refactoring (25.01.28) - Added function descriptions for better clarity and maintainability. - Introduced guidelines for each function, defining objectives and expected behavior. diff --git a/pyproject.toml b/pyproject.toml index 85776ec..1be289b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ dynamic = ["version"] authors = [{ name = "Mr. Finchum" }] description = "optima35 is a package to modify images, using pillow and piexif." readme = "pip_README.md" -requires-python = ">=3.8" +requires-python = ">=3.8, <4.0" dependencies = ["piexif", "pillow"] classifiers = [ "Development Status :: 5 - Production/Stable", From 41d81c4f7cc1d9b5cbb49eb00e4fedc7e5c68a10 Mon Sep 17 00:00:00 2001 From: Mr Finchum Date: Fri, 11 Apr 2025 12:51:26 +0200 Subject: [PATCH 5/8] ci: switching from gitlab-ci to woodpecker. --- .gitlab-ci.yml | 69 --------------------- .gitlab-ci/git/create_tag.yml | 15 ----- .gitlab-ci/versioning/gitversion.yml | 31 ---------- .woodpecker/woodpecker_ci.yml | 92 ++++++++++++++++++++++++++++ README.md | 2 + 5 files changed, 94 insertions(+), 115 deletions(-) delete mode 100644 .gitlab-ci.yml delete mode 100644 .gitlab-ci/git/create_tag.yml delete mode 100644 .gitlab-ci/versioning/gitversion.yml create mode 100644 .woodpecker/woodpecker_ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 30a2cc5..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,69 +0,0 @@ ---- -include: - - local: .gitlab-ci/versioning/gitversion.yml - - local: .gitlab-ci/git/create_tag.yml - -stages: - - build - - release - -gitversion: - extends: .versioning:gitversion - stage: .pre - tags: - - gitlab-org-docker - rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Run this job when commits are pushed or merged to the default branch - -build: - stage: build - image: python:3.9.21 - tags: - - gitlab-org-docker - rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Run this job when commits are pushed or merged to the default branch - needs: - - job: gitversion - artifacts: true - script: - - sed -i "s/1.0.0/${GitVersion_MajorMinorPatch}/" src/optima35/__init__.py - - cat src/optima35/__init__.py - - python3 -m pip install build - - python3 -m build - artifacts: - paths: - - dist/* - expire_in: 1 day - -publish: - stage: release - image: python:3.9.21 - tags: - - gitlab-org-docker - rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Run this job when commits are pushed or merged to the default branch - variables: - TWINE_USERNAME: "__token__" - TWINE_PASSWORD: $TWINE_API - needs: - - job: build - artifacts: true - script: - - python3 -m pip install twine - - python3 -m twine upload dist/* - -create_tag: - extends: .git:create_tag - stage: release - tags: - - gitlab-org-docker - variables: - VERSION: $GitVersion_SemVer - TOKEN: $GITLAB_TOKEN - needs: - - job: gitversion - artifacts: true - rules: - - if: $CI_COMMIT_TAG - when: never # Do not run this job when a tag is created manually - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Run this job when commits are pushed or merged to the default branch diff --git a/.gitlab-ci/git/create_tag.yml b/.gitlab-ci/git/create_tag.yml deleted file mode 100644 index 2c1afd7..0000000 --- a/.gitlab-ci/git/create_tag.yml +++ /dev/null @@ -1,15 +0,0 @@ ---- - -.git:create_tag: - image: alpine:3.21 - variables: - GIT_STRATEGY: clone - GIT_DEPTH: 0 - GIT_LFS_SKIP_SMUDGE: 1 - VERSION: '' - TOKEN: '' # Token with push privileges - script: - - apk add git - - git remote set-url origin https://oauth2:$TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH - - git tag $VERSION - - git push origin tag $VERSION diff --git a/.gitlab-ci/versioning/gitversion.yml b/.gitlab-ci/versioning/gitversion.yml deleted file mode 100644 index dbbc149..0000000 --- a/.gitlab-ci/versioning/gitversion.yml +++ /dev/null @@ -1,31 +0,0 @@ ---- -.versioning:gitversion: - image: - name: mcr.microsoft.com/dotnet/sdk:9.0 - variables: - GIT_STRATEGY: clone - GIT_DEPTH: 0 # force a deep/non-shallow fetch need by gitversion - GIT_LFS_SKIP_SMUDGE: 1 - cache: [] # caches and before / after scripts can mess things up - script: - - | - dotnet tool install --global GitVersion.Tool --version 5.* - export PATH="$PATH:/root/.dotnet/tools" - - dotnet-gitversion -output buildserver - - # We could just collect the output file gitversion.properties (with artifacts:report:dotenv: gitversion.properties as it is already in DOTENV format, - # however it contains ~33 variables which unnecessarily consumes many of the 50 max DOTENV variables of the free GitLab version. - # Limits are higher for licensed editions, see https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportsdotenv - grep 'GitVersion_LegacySemVer=' gitversion.properties >> gitversion.env - grep 'GitVersion_SemVer=' gitversion.properties >> gitversion.env - grep 'GitVersion_FullSemVer=' gitversion.properties >> gitversion.env - grep 'GitVersion_Major=' gitversion.properties >> gitversion.env - grep 'GitVersion_Minor=' gitversion.properties >> gitversion.env - grep 'GitVersion_Patch=' gitversion.properties >> gitversion.env - grep 'GitVersion_MajorMinorPatch=' gitversion.properties >> gitversion.env - grep 'GitVersion_BuildMetaData=' gitversion.properties >> gitversion.env - artifacts: - reports: - # propagates variables into the pipeline level - dotenv: gitversion.env diff --git a/.woodpecker/woodpecker_ci.yml b/.woodpecker/woodpecker_ci.yml new file mode 100644 index 0000000..0cc947f --- /dev/null +++ b/.woodpecker/woodpecker_ci.yml @@ -0,0 +1,92 @@ +steps: + - name: gitversion + depends_on: [] # nothing start emititly + when: + event: push + branch: main + image: mcr.microsoft.com/dotnet/sdk:9.0 + environment: + CI_TOKEN: + from_secret: CI_TOKEN + commands: + - git remote set-url origin https://CodeByMrFinchum:$CI_TOKEN@code.boxyfoxy.net/$CI_REPO.git + - git fetch --unshallow --tags + - apt-get update && apt-get install -y jq + - dotnet tool install --global GitVersion.Tool --version 5.* + - export PATH="$PATH:/root/.dotnet/tools" + - dotnet-gitversion -output json > version.json + - ls + - cat version.json + - | + echo "GitVersion_SemVer=$(jq -r '.SemVer' version.json)" >> gitversion.env + echo "GitVersion_LegacySemVer=$(jq -r '.LegacySemVer' version.json)" >> gitversion.env + echo "GitVersion_FullSemVer=$(jq -r '.FullSemVer' version.json)" >> gitversion.env + echo "GitVersion_Major=$(jq -r '.Major' version.json)" >> gitversion.env + echo "GitVersion_Minor=$(jq -r '.Minor' version.json)" >> gitversion.env + echo "GitVersion_Patch=$(jq -r '.Patch' version.json)" >> gitversion.env + echo "GitVersion_MajorMinorPatch=$(jq -r '.MajorMinorPatch' version.json)" >> gitversion.env + echo "GitVersion_BuildMetaData=$(jq -r '.BuildMetaData' version.json)" >> gitversion.env + + - name: tagging + depends_on: [gitversion] + when: + event: push + branch: main + image: alpine/git + environment: + CI_TOKEN: + from_secret: CI_TOKEN + commands: + - ls + - cat gitversion.env + - git config --global user.email "ci@noreply.boxyfoxy.net" + - git config --global user.name "CI Bot" + - git remote set-url origin https://CodeByMrFinchum:$${CI_TOKEN}@code.boxyfoxy.net/$${CI_REPO}.git + - . gitversion.env + - git tag $GitVersion_SemVer + - git push origin tag $GitVersion_SemVer + + - name: build + depends_on: [gitversion, tagging] + when: + event: push + branch: main + image: python:3.9.21 + commands: + - ls + - cat gitversion.env + - export $(cat gitversion.env | xargs) + - sed -i "s/1.0.0/${GitVersion_SemVer}/" src/optima35/__init__.py + - cat src/optima35/__init__.py + - python3 -m pip install build + - python3 -m build + + - name: publish_pypi + depends_on: [gitversion, tagging, build] + when: + event: push + branch: main + image: python:3.9.21 + environment: + TWINE_PASSWORD: + from_secret: TWINE_API + TWINE_USERNAME: "__token__" + commands: + - ls + - python3 -m pip install twine + - python3 -m twine upload dist/* + + - name: publish_forgejo + depends_on: [gitversion, tagging, build] + when: + event: push + branch: main + image: python:3.9.21 + environment: + TWINE_PASSWORD: + from_secret: PKG_TOKEN + TWINE_USERNAME: "CodeByMrFinchum" + commands: + - ls + - python3 -m pip install twine + - python3 -m twine upload --repository-url https://code.boxyfoxy.net/api/packages/CodeByMrFinchum/pypi dist/* diff --git a/README.md b/README.md index ebd7622..0b6d4d3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # **OPTIMA35** +Developed on my [forgejo instance](https://code.boxyfoxy.net/CodeByMrFinchum), [GitLab](https://gitlab.com/CodeByMrFinchum) is used as backup. + **OPTIMA35** stands for **Organizing, Processing, Tweaking Images, and Modifying Analogs from 35mm Film**. It is a Python package designed to simplify image editing and metadata management, providing an interface/API for handling image and EXIF data seamlessly. While OPTIMA35 was created with analog photography in mind—where scanned images often lack proper EXIF data or retain only scanner metadata—it is equally useful for digital images. Adding or managing EXIF data is invaluable for organizing private photo collections, making your photo library more structured and searchable. OPTIMA35 is a core package that acts as an interface for libraries like Pillow and piexif, simplifying image manipulation tasks. While it modifies images one at a time, it requires a dedicated program for flexible and batch processing. For a user-friendly graphical experience, consider using [OptimaLab35](https://gitlab.com/CodeByMrFinchum/OptimaLab35), a GUI designed specifically for OPTIMA35, also developed by me. From 9535b869af86730a8faf8837e6cc4f124e919371 Mon Sep 17 00:00:00 2001 From: Mr Finchum Date: Fri, 11 Apr 2025 13:03:08 +0200 Subject: [PATCH 6/8] fix: fixes version insection --- .woodpecker/woodpecker_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker/woodpecker_ci.yml b/.woodpecker/woodpecker_ci.yml index 0cc947f..d77a4c6 100644 --- a/.woodpecker/woodpecker_ci.yml +++ b/.woodpecker/woodpecker_ci.yml @@ -56,7 +56,7 @@ steps: - ls - cat gitversion.env - export $(cat gitversion.env | xargs) - - sed -i "s/1.0.0/${GitVersion_SemVer}/" src/optima35/__init__.py + - sed -i "s/1.0.0/$GitVersion_SemVer/" src/optima35/__init__.py - cat src/optima35/__init__.py - python3 -m pip install build - python3 -m build From 9869a9e419b61f6fba6dff9c6a971c216fbe8d18 Mon Sep 17 00:00:00 2001 From: Mr Finchum Date: Fri, 11 Apr 2025 13:09:48 +0200 Subject: [PATCH 7/8] patch: added missing entry. --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4ceb7e..6679ae2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 1.1.x: Migration (25.04.11) +- Migrated from GitLab to my forgejo instance for development. + +--- + ## 1.0.x ### 1.0.3 Patch: Adjusted pyproject.toml (25.03.24) - Added <4.0 python version From daba1108052c5e11c67f2fda7dceee80f6b00037 Mon Sep 17 00:00:00 2001 From: Mr Finchum Date: Fri, 11 Apr 2025 13:17:54 +0200 Subject: [PATCH 8/8] patch: adjusting pip readme. --- pip_README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pip_README.md b/pip_README.md index afe32fa..2f7312b 100644 --- a/pip_README.md +++ b/pip_README.md @@ -1,2 +1,2 @@ -Uses pillow and piexif to modify images, see [optima35](https://gitlab.com/CodeByMrFinchum/optima35) gitlab for more information. +Uses pillow and piexif to modify images, see my [forgejo instance](https://code.boxyfoxy.net/CodeByMrFinchum) or [GitLab](https://gitlab.com/CodeByMrFinchum)(backup) for more information. Install [OptimaLab35](https://pypi.org/project/OptimaLab35/) in pip for a GUI.