Skip to content

Commit dae8573

Browse files
authored
Create Image_Convert_BUSINESS_PRO_v2.py
1 parent ab198bc commit dae8573

1 file changed

Lines changed: 302 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)