├── LICENSE ├── README.md └── CaptionIMG.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Antonio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CaptionIMG 2 | Simple program written in python to manually caption your images (or any other file types) so you can use them for AI training. 3 | I use it for Dreambooth training (StableDiffusion). 4 | 5 | 6 | The captions will be saved as text files in the same location as the loaded images with the same name as the image + .txt (cat.jpg -> cat.txt). 7 | It will also load existing captions and will be shown in the text area. 8 | 9 | Tested on Windows 11 and Ubuntu 22.10 10 | 11 | You need to install Pillow: 12 | pip install pillow 13 | 14 | Make sure your python build has tkinter installed. 15 | 16 | It will probably have some bugs. 17 | 18 | 19 | # Easy steps: 20 | 21 | 1. Run the program. 22 | 2. Open the images. 23 | 3. Click on any image on the list. 24 | 4. Write the captions. 25 | 5. Save. 26 | 6. Give yourself a high five for a job well done, then proceed to eat some snacks in celebration. 27 | 28 | # TODO: 29 | Make the GUI dynamically resizable. 30 | 31 | # Some Images: 32 | 33 | 34 | 35 | ![imagen](https://user-images.githubusercontent.com/1978099/218338029-6d7d09c9-c478-41ff-a0db-07183b01e06e.png) 36 | 37 | 38 | ![imagen](https://user-images.githubusercontent.com/1978099/218338051-140e3661-43cc-45b9-976f-da1511cf4328.png) 39 | 40 | 41 | ![imagen](https://user-images.githubusercontent.com/1978099/218338921-d5c9f322-70cd-4c06-9ee9-1b8d2466218d.png) 42 | 43 | (Yes, I use Notepad++) 44 | -------------------------------------------------------------------------------- /CaptionIMG.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tkinter as tk 3 | from tkinter import filedialog 4 | from tkinter import messagebox 5 | from tkinter import Listbox 6 | from PIL import Image, ImageTk 7 | import re 8 | 9 | Image.MAX_IMAGE_PIXELS = None 10 | 11 | def natural_sort(l): 12 | convert = lambda text: int(text) if text.isdigit() else text.lower() 13 | alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)] 14 | return sorted(l, key = alphanum_key) 15 | 16 | class CaptionIMG: 17 | def __init__(self, root): 18 | 19 | self.root = root 20 | screen_width = root.winfo_screenwidth() 21 | screen_height = root.winfo_screenheight() 22 | 23 | #self.root.wm_attributes('-toolwindow', 'True') 24 | self.root.title("CaptionIMG by ANTONIOPS") 25 | self.root.geometry(f"{int(screen_width/1.5)}x{int(screen_height/1.5)}") 26 | self.root.resizable(False,False) 27 | 28 | self.frame_list = tk.Frame(self.root) 29 | self.frame_list.pack(side='left', fill='y') 30 | 31 | self.image_list = Listbox(self.frame_list, width=30) 32 | 33 | self.image_list.bind('<>', self.load_image) 34 | self.image_list.bind('', self.save) 35 | 36 | self.horizontal_scrollbar = tk.Scrollbar(self.frame_list, orient='horizontal') 37 | self.horizontal_scrollbar.pack(side='bottom', fill='x') 38 | self.horizontal_scrollbar.config(command=self.image_list.xview) 39 | self.image_list.config(xscrollcommand=self.horizontal_scrollbar.set) 40 | 41 | self.vertical_scrollbar = tk.Scrollbar(self.frame_list, orient='vertical') 42 | self.vertical_scrollbar.pack(side='right', fill='y') 43 | self.vertical_scrollbar.config(command=self.image_list.yview) 44 | self.image_list.config(yscrollcommand=self.vertical_scrollbar.set) 45 | 46 | self.image_list.pack(side='left', fill='both') 47 | self.image_label = tk.Label(self.root) 48 | self.image_label.pack(side='top', anchor='center') 49 | self.image_label.pack_propagate(False) 50 | 51 | self.text_entry = tk.Text(self.root, height=6, width=85, wrap='word') 52 | self.text_entry.config(borderwidth=5, relief="groove") 53 | self.text_entry.pack(side='bottom', fill='both') 54 | 55 | self.save_button = tk.Button(self.root, text="Save Captions", command=self.save) 56 | self.save_button.pack(side='bottom') 57 | self.text_entry.bind('', self.save) 58 | 59 | self.open_button = tk.Button(self.root, text="Open Images", command=self.open_images) 60 | self.open_button.pack(side='bottom') 61 | 62 | def open_images(self): 63 | try: 64 | file_types = "*.bmp *.jpg *.jpeg *.png" 65 | file_paths = filedialog.askopenfilenames(filetypes=[("Common Image Files", file_types), ("All", "*.*")]) 66 | file_paths = natural_sort(file_paths) 67 | if file_paths: 68 | self.image_list.delete('0','end') 69 | self.file_map = {} 70 | for file_path in file_paths: 71 | file_name = os.path.basename(file_path) 72 | self.file_map[file_name] = file_path 73 | self.image_list.insert('end', file_name) 74 | except: 75 | pass 76 | 77 | def load_image(self, event): 78 | try: 79 | selection = self.image_list.curselection() 80 | if not selection: 81 | return 82 | 83 | self.text_entry.delete(1.0, 'end') 84 | index = selection[0] 85 | file_name = self.image_list.get(index) 86 | file_path = self.file_map[file_name] 87 | self.current_image = file_name 88 | self.current_image_path = file_path 89 | 90 | 91 | screen_width = root.winfo_screenwidth() 92 | screen_height = root.winfo_screenheight() 93 | 94 | max_size = int(screen_width/2), int(screen_height/2.1) 95 | 96 | image = Image.open(file_path) 97 | 98 | original_width, original_height = image.size 99 | aspect_ratio = original_width / original_height 100 | new_width, new_height = max_size 101 | 102 | if original_width > original_height: 103 | new_height = int(new_width / aspect_ratio) 104 | else: 105 | new_width = int(new_height * aspect_ratio) 106 | 107 | if new_width > max_size[0]: 108 | new_width = max_size[0] 109 | new_height = int(new_width / aspect_ratio) 110 | if new_height > max_size[1]: 111 | new_height = max_size[1] 112 | new_width = int(new_height * aspect_ratio) 113 | 114 | image = image.resize((new_width, new_height), Image.LANCZOS) 115 | image = ImageTk.PhotoImage(image) 116 | self.image_label.config(image=image) 117 | self.image_label.image = image 118 | self.image_label.config(borderwidth=5, relief="groove") 119 | 120 | description_file = str(self.current_image_path).rsplit('.', 1)[0] + ".txt" 121 | 122 | if os.path.isfile(description_file): 123 | with open(description_file, "r") as file: 124 | description = file.read() 125 | self.text_entry.insert(1.0, description) 126 | else: 127 | self.text_entry.delete(1.0, 'end') 128 | except: 129 | pass 130 | 131 | def save(self, event=None): 132 | try: 133 | description = self.text_entry.get(1.0, 'end') 134 | description_file = str(self.current_image_path).rsplit('.', 1)[0] + ".txt" 135 | with open(description_file, "w") as file: 136 | file.write(description) 137 | messagebox.showinfo("Success", f"Captions saved successfully at {description_file}") 138 | except: 139 | messagebox.showinfo("Error", f"There was an error while saving the captions") 140 | 141 | 142 | root = tk.Tk() 143 | app = CaptionIMG(root) 144 | root.mainloop() 145 | --------------------------------------------------------------------------------