Skip to content

Commit b8d82cb

Browse files
author
dn0z
committed
Major changes on the imgedit structure, added threads etc
1 parent 1d46320 commit b8d82cb

File tree

2 files changed

+172
-137
lines changed

2 files changed

+172
-137
lines changed

bir.py

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
# https://github.com/dn0z/Batch-Image-Resize
66

77
import imgedit
8+
89
import os
10+
import threading, queue
911

1012
from enum import Enum
1113
from tkinter import *
@@ -62,13 +64,57 @@ def confirm_settings(self):
6264

6365
return messagebox.askyesno("Export confirmation", confirm_msg)
6466

65-
def increase_progress_bar_value(self):
67+
def exporting_interval(self, q):
68+
"""
69+
Run @ a 100 ms interval using tkinter's `.after()` while the exporting thread is running
70+
71+
In order to keep tkinter's event loop running, our image editing code runs
72+
on a different thread. We look at `num_of_exported_images`, `images_to_export`
73+
and the result of our `q` queue (which represents if our code failed to
74+
open, resize and/or save any images) to check the status of the thread
75+
76+
Once everything is done, we will get the operation's result using `q.get()`
77+
and we will call the `self.exported()` (passing the result) to handle the rest
78+
79+
:param q: our `Queue` instance
6680
"""
67-
Increase the progress bar value by one
81+
82+
run_the_interval = True
83+
84+
if self.img_edit.num_of_images_to_export is not None:
85+
exported_images = self.img_edit.num_of_exported_images
86+
images_to_export = self.img_edit.num_of_images_to_export
87+
88+
# update progress bar
89+
self.progress_bar["value"] = exported_images
90+
self.progress_bar["maximum"] = images_to_export
91+
92+
# check if we are done
93+
if exported_images >= images_to_export:
94+
run_the_interval = False
95+
self.exported(q.get())
96+
97+
if run_the_interval:
98+
self.after(100, self.exporting_interval, q)
99+
100+
def exported(self, result):
68101
"""
102+
Display a success or an error message depending on the operation's result
69103
70-
self.progress_bar["value"] += 1
71-
print("Increased progress bar value by one")
104+
Close the progress window first, then display the message box
105+
106+
:param result: the result of the exports (`True` if everything is okay,
107+
or `False` if there was an error)
108+
"""
109+
110+
self.clear_progress_window()
111+
112+
if result:
113+
messagebox.showinfo("Exports completed",
114+
"All images were exported successfully")
115+
else:
116+
messagebox.showwarning("Exports failed",
117+
"One or more images failed to export")
72118

73119
def clear_progress_window(self):
74120
"""
@@ -78,11 +124,9 @@ def clear_progress_window(self):
78124
if self.progress_window is not None:
79125
self.progress_window.destroy()
80126

81-
def display_progress_window(self, progress_maximum=100):
127+
def display_progress_window(self):
82128
"""
83129
Display the progress window
84-
85-
:param progress_maximum: The maximum value of the progress bar (default is 100)
86130
"""
87131

88132
self.clear_progress_window()
@@ -101,9 +145,6 @@ def display_progress_window(self, progress_maximum=100):
101145
mode="determinate")
102146
self.progress_bar.pack(expand=True, fill="both", side="bottom")
103147
self.progress_bar["value"] = 0
104-
self.progress_bar["maximum"] = progress_maximum
105-
106-
self.progress_window.update()
107148

108149
def get_settings_status(self):
109150
"""
@@ -157,31 +198,22 @@ def export_button_handler(self):
157198
else:
158199
# Valid settings, confirm settings with the user and export
159200
if self.confirm_settings():
160-
num_of_images = imgedit.image_files_in_dir(self.selected_directory.get())
161-
self.display_progress_window(num_of_images)
201+
# num_of_images = imgedit.image_files_in_dir(self.selected_directory.get())
202+
203+
self.display_progress_window()
162204
print("Progress window is on screen")
163205

164-
print("Exporting images")
165-
result = imgedit.export_all_in_dir(
166-
self.selected_directory.get(),
167-
int(self.export_properties["width"].get()),
168-
int(self.export_properties["height"].get()),
169-
self.export_properties["type"].get(),
170-
self.overwrite_original.get(),
171-
self.increase_progress_bar_value
172-
)
173-
174-
self.clear_progress_window()
175-
print("Progress window is cleared")
176-
177-
if result:
178-
# at this point, we are done with our exports, display a success message
179-
messagebox.showinfo("Exports completed",
180-
"All images were exported successfully")
181-
else:
182-
# one or more images failed to export, display a warning
183-
messagebox.showwarning("Exports failed",
184-
"One or more images failed to export")
206+
q = queue.Queue()
207+
my_thread = threading.Thread(target=self.img_edit.export_all_in_dir,
208+
args=(self.selected_directory.get(),
209+
int(self.export_properties["width"].get()),
210+
int(self.export_properties["height"].get()),
211+
self.export_properties["type"].get(),
212+
self.overwrite_original.get(),
213+
q)
214+
)
215+
my_thread.start()
216+
self.exporting_interval(q)
185217

186218
def browse_for_directory(self):
187219
"""
@@ -290,6 +322,8 @@ def __init__(self, parent=None):
290322
self.progress_window = None
291323
self.progress_bar = None
292324

325+
self.img_edit = imgedit.ImgEdit()
326+
293327
self.selected_directory = StringVar(self)
294328
self.overwrite_original = BooleanVar(self)
295329
self.export_properties = {

imgedit.py

Lines changed: 105 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -8,107 +8,108 @@
88
import PIL.Image
99

1010

11-
def is_image(filename):
12-
"""
13-
Checks if a filename is an image (png, jpg or jpeg, case insensitive)
14-
15-
:param filename: The filename (as a string)
16-
:return: `True` if the given filename is an image, or `False` if it's not
17-
"""
18-
19-
file = filename.lower()
20-
return file.endswith(".png") or file.endswith(".jpg") or file.endswith(".jpeg")
21-
22-
23-
def get_filename_with_type(filename, file_type, suffix=""):
24-
"""
25-
Get a filename and return it with the given suffix and the correct file extension for the given type
26-
27-
:param filename: The filename (e.g. "image_file.jpg")
28-
:param file_type: The file type (e.g. "PNG")
29-
:param suffix: An optional string to place between the name and the extension of the file (default is "")
30-
:return: the filename with the correct extension for the given type (e.g. "image_file.png")
31-
"""
32-
33-
extension = filename.split(".")[-1]
34-
return filename[:-(len(extension) + 1)] + suffix + "." + file_type.lower()
35-
36-
37-
def export_file(path, name, width, height, export_type, overwrite):
38-
"""
39-
Open, resize and save an image with the given properties
40-
41-
:param path: The path to the directory where the image is located (without the image filename)
42-
:param name: The filename of the image we want to export
43-
:param width: The new width we want to resize to
44-
:param height: The new height we want to resize to
45-
:param export_type: The file type we want to save to (we ignore it if `overwrite` is `True`)
46-
:param overwrite: Whether we want to overwrite the original files or not
47-
"""
48-
49-
img_path = os.path.join(path, name)
50-
51-
# set the destination image file we want to save
52-
dest_img_name = get_filename_with_type(name, export_type, "_resize")
53-
if overwrite:
54-
dest_img_name = name
55-
56-
try:
57-
# open the given image, resize and save it
58-
img = PIL.Image.open(img_path)
59-
img = img.resize((width, height), PIL.Image.ANTIALIAS)
60-
img.save(os.path.join(path, dest_img_name))
61-
except IOError:
62-
return False
63-
64-
return True
65-
66-
67-
def image_files_in_dir(selected_dir):
68-
"""
69-
Returns the number of image files in the given directory
70-
71-
:param selected_dir: The directory containing the images
72-
:return: the number of image files the directory contains
73-
"""
74-
images = 0
75-
76-
for path, subdirs, files in os.walk(selected_dir):
77-
for name in files:
78-
if is_image(name):
79-
images += 1
80-
81-
return images
82-
83-
84-
def export_all_in_dir(selected_dir, width, height, export_type, overwrite, progress_bar_callback=None):
85-
"""
86-
Export all the images in the selected directory
87-
88-
When `self.init_export()` is called, everything is ready to resize and export images
89-
This loops through all the files in the given directory and calls `self.export_file()`
90-
for the actual opening, resizing and saving of the files
91-
92-
:param selected_dir: The path to the directory containing all the images to resize
93-
:param width: The new width we want to resize to
94-
:param height: The new height we want to resize to
95-
:param export_type: The file type we want to save to (we ignore it if `overwrite` is `True`)
96-
:param overwrite: Whether we want to overwrite the original files or not
97-
:return: `True` if all images were exported successfully or `False` if there was an error
98-
"""
99-
100-
all_exported_successfully = True
101-
102-
for path, subdirs, files in os.walk(selected_dir):
103-
for name in files:
104-
if is_image(name):
105-
# export and check if everything went okay
106-
exported_successfully = export_file(path, name, width, height, export_type, overwrite)
107-
if not exported_successfully:
108-
all_exported_successfully = False
109-
110-
# update the progress bar
111-
if progress_bar_callback is not None:
112-
progress_bar_callback()
113-
114-
return all_exported_successfully
11+
class HelpingMethods(object):
12+
@staticmethod
13+
def is_image(filename):
14+
"""
15+
Checks if a filename is an image (png, jpg or jpeg, case insensitive)
16+
17+
:param filename: The filename (as a string)
18+
:return: `True` if the given filename is an image, or `False` if it's not
19+
"""
20+
21+
file = filename.lower()
22+
return file.endswith(".png") or file.endswith(".jpg") or file.endswith(".jpeg")
23+
24+
@staticmethod
25+
def get_filename_with_type(filename, file_type, suffix=""):
26+
"""
27+
Get a filename and return it with the given suffix and the correct file extension for the given type
28+
29+
:param filename: The filename (e.g. "image_file.jpg")
30+
:param file_type: The file type (e.g. "PNG")
31+
:param suffix: An optional string to place between the name and the extension of the file (default is "")
32+
:return: the filename with the correct extension for the given type (e.g. "image_file.png")
33+
"""
34+
35+
extension = filename.split(".")[-1]
36+
return filename[:-(len(extension) + 1)] + suffix + "." + file_type.lower()
37+
38+
39+
class ImgEdit(object):
40+
def export_file(self, path, name, width, height, export_type, overwrite):
41+
"""
42+
Open, resize and save an image with the given properties
43+
44+
:param path: The path to the directory where the image is located (without the image filename)
45+
:param name: The filename of the image we want to export
46+
:param width: The new width we want to resize to
47+
:param height: The new height we want to resize to
48+
:param export_type: The file type we want to save to (we ignore it if `overwrite` is `True`)
49+
:param overwrite: Whether we want to overwrite the original files or not
50+
"""
51+
52+
img_path = os.path.join(path, name)
53+
54+
# set the destination image file we want to save
55+
dest_img_name = HelpingMethods.get_filename_with_type(name, export_type, "_resize")
56+
if overwrite:
57+
dest_img_name = name
58+
59+
try:
60+
# open the given image, resize and save it
61+
img = PIL.Image.open(img_path)
62+
img = img.resize((width, height), PIL.Image.ANTIALIAS)
63+
img.save(os.path.join(path, dest_img_name))
64+
except IOError:
65+
return False
66+
67+
return True
68+
69+
def export_all_in_dir(self, selected_dir, width, height, export_type, overwrite, q):
70+
"""
71+
Export all the images in the selected directory
72+
73+
When `self.init_export()` is called, everything is ready to resize and export images
74+
This loops through all the files in the given directory and calls `self.export_file()`
75+
for the actual opening, resizing and saving of the files
76+
77+
:param selected_dir: The path to the directory containing all the images to resize
78+
:param width: The new width we want to resize to
79+
:param height: The new height we want to resize to
80+
:param export_type: The file type we want to save to (we ignore it if `overwrite` is `True`)
81+
:param overwrite: Whether we want to overwrite the original files or not
82+
:return: `True` if all images were exported successfully or `False` if there was an error
83+
"""
84+
85+
# reset default values
86+
self.num_of_exported_images = 0
87+
self.num_of_images_to_export = None
88+
89+
# loop through all the files in the given directory and store the
90+
# path and the filename of image files in a list
91+
images = []
92+
for path, subdirs, files in os.walk(selected_dir):
93+
for name in files:
94+
if HelpingMethods.is_image(name):
95+
images.append({
96+
"path": path,
97+
"name": name
98+
})
99+
self.num_of_images_to_export = len(images)
100+
101+
# loop through the images list to open, resize and save them
102+
all_exported_successfully = True
103+
for img in images:
104+
# export and check if everything went okay
105+
exported_successfully = self.export_file(img["path"], img["name"], width, height, export_type, overwrite)
106+
if not exported_successfully:
107+
all_exported_successfully = False
108+
109+
self.num_of_exported_images += 1
110+
111+
q.put(all_exported_successfully)
112+
113+
def __init__(self):
114+
self.num_of_exported_images = 0
115+
self.num_of_images_to_export = None

0 commit comments

Comments
 (0)