Skip to content

Commit 62f424f

Browse files
authored
Create ImageConvertPRO.py
1 parent e022b2f commit 62f424f

1 file changed

Lines changed: 318 additions & 0 deletions

File tree

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
import os
2+
import sys
3+
import sqlite3
4+
from threading import Thread
5+
from PIL import Image, ImageTk
6+
7+
try:
8+
from PIL import ImageResampling
9+
RESAMPLE = ImageResampling.LANCZOS
10+
except:
11+
RESAMPLE = Image.LANCZOS
12+
13+
import ttkbootstrap as tb
14+
from ttkbootstrap.constants import *
15+
from tkinter import filedialog, messagebox, Listbox, Canvas, Scrollbar
16+
from tkinterdnd2 import TkinterDnD, DND_FILES
17+
18+
# ---------------- APP INFO ----------------
19+
APP_NAME = "ImageConvert PRO"
20+
APP_VERSION = "1.1"
21+
22+
# ---------------- PATH ----------------
23+
BASE_DIR = os.path.dirname(sys.argv[0])
24+
DB_NAME = os.path.join(BASE_DIR, "snapconvert.db")
25+
OUTPUT_DIR = os.path.join(BASE_DIR, "converted")
26+
27+
# ---------------- DATABASE ----------------
28+
def init_db():
29+
conn = sqlite3.connect(DB_NAME)
30+
c = conn.cursor()
31+
c.execute("""CREATE TABLE IF NOT EXISTS history(
32+
id INTEGER PRIMARY KEY,
33+
name TEXT,
34+
original TEXT,
35+
converted TEXT)""")
36+
conn.commit()
37+
conn.close()
38+
39+
def insert_db(name, orig, conv):
40+
conn = sqlite3.connect(DB_NAME)
41+
c = conn.cursor()
42+
c.execute("INSERT INTO history(name, original, converted) VALUES(?,?,?)",(name,orig,conv))
43+
conn.commit()
44+
conn.close()
45+
46+
def fetch_db():
47+
conn = sqlite3.connect(DB_NAME)
48+
c = conn.cursor()
49+
c.execute("SELECT name, original, converted FROM history ORDER BY id DESC")
50+
rows = c.fetchall()
51+
conn.close()
52+
return rows
53+
54+
def clear_history():
55+
conn = sqlite3.connect(DB_NAME)
56+
c = conn.cursor()
57+
c.execute("DELETE FROM history")
58+
conn.commit()
59+
conn.close()
60+
61+
# ---------------- ABOUT ----------------
62+
def show_about():
63+
messagebox.showinfo(
64+
f"About {APP_NAME}",
65+
f"{APP_NAME} v{APP_VERSION}\n\n"
66+
"Professional Image Converter\n\n"
67+
"© 2026 Mate Technologies\n"
68+
"https://matetools.gumroad.com"
69+
)
70+
71+
# ---------------- WORKER ----------------
72+
def worker(images, fmt, out, quality, resize, keep, progress, finish):
73+
os.makedirs(out, exist_ok=True)
74+
total = len(images)
75+
count = 0
76+
77+
for i, path in enumerate(images):
78+
try:
79+
with Image.open(path) as img:
80+
if resize > 0:
81+
img = img.resize((resize, resize), RESAMPLE)
82+
83+
if fmt == "JPEG" and img.mode in ("RGBA","P"):
84+
img = img.convert("RGB")
85+
86+
name = os.path.splitext(os.path.basename(path))[0]
87+
if not keep:
88+
name += f"_{i+1}"
89+
90+
out_path = os.path.join(out, f"{name}.{fmt.lower()}")
91+
92+
c = 1
93+
while os.path.exists(out_path):
94+
out_path = os.path.join(out, f"{name}_{c}.{fmt.lower()}")
95+
c += 1
96+
97+
params = {"quality": quality} if fmt == "JPEG" else {}
98+
img.save(out_path, fmt, **params)
99+
100+
insert_db(name, path, out_path)
101+
count += 1
102+
103+
except Exception as e:
104+
print("Error:", e)
105+
106+
progress(int((i+1)/total*100))
107+
108+
finish(count)
109+
110+
# ---------------- APP ----------------
111+
class App:
112+
def __init__(self):
113+
self.root = TkinterDnD.Tk()
114+
self.root.title(APP_NAME)
115+
self.root.geometry("1200x750")
116+
self.style = tb.Style("darkly")
117+
118+
self.images = []
119+
self.thumbs = []
120+
121+
self.create_menu()
122+
self.build_ui()
123+
self.load_history()
124+
125+
self.root.drop_target_register(DND_FILES)
126+
self.root.dnd_bind("<<Drop>>", self.drop)
127+
128+
# MENU
129+
def create_menu(self):
130+
menubar = tb.Menu(self.root)
131+
help_menu = tb.Menu(menubar, tearoff=0)
132+
help_menu.add_command(label="About", command=show_about)
133+
menubar.add_cascade(label="Help", menu=help_menu)
134+
self.root.config(menu=menubar)
135+
136+
# UI
137+
def build_ui(self):
138+
main = tb.Frame(self.root)
139+
main.pack(fill=BOTH, expand=True)
140+
141+
# LEFT
142+
left = tb.Frame(main, width=250, padding=5)
143+
left.pack(side=LEFT, fill=Y)
144+
145+
tb.Label(left, text="📂 Files", font=("Arial", 12)).pack(pady=5)
146+
147+
self.listbox = Listbox(left, bg="#1e1e1e", fg="white")
148+
self.listbox.pack(fill=BOTH, expand=True, pady=5)
149+
150+
tb.Button(left, text="Add Images", command=self.add_files, bootstyle=SUCCESS).pack(fill=X, pady=2)
151+
tb.Button(left, text="Add Folder", command=self.add_folder, bootstyle=INFO).pack(fill=X, pady=2)
152+
tb.Button(left, text="Remove", command=self.remove_selected, bootstyle=DANGER).pack(fill=X, pady=2)
153+
tb.Button(left, text="Clear All", command=self.clear_all, bootstyle=SECONDARY).pack(fill=X, pady=2)
154+
155+
# CENTER
156+
center = tb.Frame(main, padding=5)
157+
center.pack(side=LEFT, fill=BOTH, expand=True)
158+
159+
self.canvas = Canvas(center, bg="#121212")
160+
self.scroll = Scrollbar(center, command=self.canvas.yview)
161+
162+
self.inner = tb.Frame(self.canvas)
163+
164+
self.inner.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))
165+
166+
self.canvas.create_window((0,0), window=self.inner, anchor="nw")
167+
self.canvas.configure(yscrollcommand=self.scroll.set)
168+
169+
self.canvas.pack(side=LEFT, fill=BOTH, expand=True)
170+
self.scroll.pack(side=RIGHT, fill=Y)
171+
172+
# RIGHT
173+
right = tb.Frame(main, width=260, padding=10)
174+
right.pack(side=RIGHT, fill=Y)
175+
176+
tb.Label(right, text="⚙ Settings", font=("Arial", 12)).pack(pady=5)
177+
178+
tb.Label(right, text="Format").pack(anchor="w")
179+
self.format = tb.Combobox(right, values=["PNG","JPEG","WEBP","BMP","TIFF"])
180+
self.format.current(0)
181+
self.format.pack(fill=X, pady=5)
182+
183+
tb.Label(right, text="JPEG Quality").pack(anchor="w")
184+
self.quality = tb.Spinbox(right, from_=10, to=100)
185+
self.quality.set(90)
186+
self.quality.pack(fill=X, pady=5)
187+
188+
tb.Label(right, text="Resize (px)").pack(anchor="w")
189+
self.resize = tb.Spinbox(right, from_=0, to=5000)
190+
self.resize.set(0)
191+
self.resize.pack(fill=X, pady=5)
192+
193+
self.keep = tb.Checkbutton(right, text="Keep original filename")
194+
self.keep.invoke()
195+
self.keep.pack(pady=5)
196+
197+
tb.Separator(right).pack(fill=X, pady=10)
198+
199+
tb.Button(right, text="🚀 Convert", command=self.convert, bootstyle=WARNING).pack(fill=X, pady=5)
200+
201+
self.progress = tb.Progressbar(right)
202+
self.progress.pack(fill=X, pady=5)
203+
204+
self.status = tb.Label(right, text="Ready")
205+
self.status.pack(pady=5)
206+
207+
tb.Separator(right).pack(fill=X, pady=10)
208+
209+
tb.Button(right, text="🧹 Delete History", command=self.delete_history, bootstyle=DANGER).pack(fill=X, pady=5)
210+
211+
# HISTORY TABLE
212+
self.table = tb.Treeview(self.root, columns=("n","o","c"), show="headings")
213+
self.table.heading("n", text="Name")
214+
self.table.heading("o", text="Original")
215+
self.table.heading("c", text="Converted")
216+
self.table.pack(fill=BOTH, expand=True)
217+
218+
# FUNCTIONS
219+
def drop(self, e):
220+
self.add_images(self.root.tk.splitlist(e.data))
221+
222+
def add_files(self):
223+
self.add_images(filedialog.askopenfilenames())
224+
225+
def add_folder(self):
226+
folder = filedialog.askdirectory()
227+
imgs = []
228+
for r,_,f in os.walk(folder):
229+
for x in f:
230+
if x.lower().endswith(("png","jpg","jpeg","bmp","gif")):
231+
imgs.append(os.path.join(r,x))
232+
self.add_images(imgs)
233+
234+
def add_images(self, paths):
235+
for p in paths:
236+
if p not in self.images:
237+
self.images.append(p)
238+
self.listbox.insert(END, os.path.basename(p))
239+
self.render_gallery()
240+
241+
def remove_selected(self):
242+
sel = list(self.listbox.curselection())
243+
sel.reverse()
244+
for i in sel:
245+
self.images.pop(i)
246+
self.listbox.delete(i)
247+
self.render_gallery()
248+
249+
def clear_all(self):
250+
self.images = []
251+
self.listbox.delete(0, END)
252+
for w in self.inner.winfo_children():
253+
w.destroy()
254+
255+
def render_gallery(self):
256+
for w in self.inner.winfo_children():
257+
w.destroy()
258+
self.thumbs.clear()
259+
260+
cols = 4
261+
for i, path in enumerate(self.images[:50]):
262+
try:
263+
img = Image.open(path)
264+
img.thumbnail((150,150))
265+
tkimg = ImageTk.PhotoImage(img)
266+
self.thumbs.append(tkimg)
267+
268+
frame = tb.Frame(self.inner)
269+
frame.grid(row=i//cols, column=i%cols, padx=10, pady=10)
270+
271+
tb.Label(frame, image=tkimg).pack()
272+
tb.Label(frame, text=os.path.basename(path), wraplength=140).pack()
273+
except:
274+
pass
275+
276+
def convert(self):
277+
if not self.images:
278+
messagebox.showwarning("No images", "Add images first")
279+
return
280+
281+
fmt = self.format.get()
282+
q = int(self.quality.get())
283+
r = int(self.resize.get())
284+
keep = self.keep.instate(["selected"])
285+
286+
def prog(v):
287+
self.root.after(0, lambda: (
288+
self.progress.config(value=v),
289+
self.status.config(text=f"{v}%")
290+
))
291+
292+
def done(c):
293+
self.root.after(0, lambda: (
294+
self.status.config(text=f"Done: {c} images"),
295+
self.progress.config(value=0),
296+
self.load_history()
297+
))
298+
299+
Thread(target=worker, args=(self.images,fmt,OUTPUT_DIR,q,r,keep,prog,done), daemon=True).start()
300+
301+
def delete_history(self):
302+
if messagebox.askyesno("Confirm", "Delete all history?"):
303+
clear_history()
304+
self.load_history()
305+
306+
def load_history(self):
307+
for i in self.table.get_children():
308+
self.table.delete(i)
309+
for row in fetch_db():
310+
self.table.insert("",END,values=row)
311+
312+
def run(self):
313+
self.root.mainloop()
314+
315+
# RUN
316+
if __name__ == "__main__":
317+
init_db()
318+
App().run()

0 commit comments

Comments
 (0)