scan2doc-pro-windows / scan2doc_gui_pro.py
Usabolfazl's picture
Upload scan2doc_gui_pro.py with huggingface_hub
d323708 verified
Raw
History Blame Contribute Delete
12.5 kB
#!/usr/bin/env python3
"""
Scan2Doc Pro v6.0 β€” GUI
Cyberpunk Red-Dark Theme
Created by Us.Abolfazl
"""
import os
import sys
import threading
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from scan2doc_pro_v6 import process_file, SUPPORTED_EXTS, find_tesseract
# ─── Theme Colors ─────────────────────────────────────────────────
BG_DARK = "#0d0d0d"
BG_CARD = "#1a1a1a"
BG_INPUT = "#252525"
RED = "#ff1744"
RED_HOVER = "#ff5252"
RED_DARK = "#b71c1c"
WHITE = "#ffffff"
GRAY = "#b0b0b0"
DIM = "#666666"
GREEN = "#00e676"
BORDER = "#333333"
class Scan2DocGUI:
def __init__(self):
self.root = tk.Tk()
self.root.title("Scan2Doc Pro v6.0 β€” Created by Us.Abolfazl")
self.root.geometry("850x700")
self.root.minsize(700, 600)
self.root.configure(bg=BG_DARK)
# Icon
icon_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "icon.ico")
if os.path.exists(icon_path):
try:
self.root.iconbitmap(icon_path)
except Exception:
pass
self.files = []
self.processing = False
self._build_ui()
def _build_ui(self):
# ─── Header ─────────────────────────────────────────────
hdr = tk.Frame(self.root, bg=BG_CARD, bd=0, highlightthickness=0)
hdr.pack(fill=tk.X, padx=10, pady=(10, 5))
tk.Label(hdr, text="⚑ Scan2Doc Pro v6.0",
font=("Arial", 20, "bold"), fg=RED, bg=BG_CARD).pack(anchor="w", padx=15, pady=8)
tk.Label(hdr, text="Created by Us.Abolfazl",
font=("Arial", 11), fg=DIM, bg=BG_CARD).pack(anchor="w", padx=15, pady=(0, 8))
# ─── File List ──────────────────────────────────────────
file_frame = tk.Frame(self.root, bg=BG_CARD, bd=0, highlightthickness=0)
file_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
tk.Label(file_frame, text="πŸ“ Input Files",
font=("Arial", 13, "bold"), fg=RED, bg=BG_CARD).pack(anchor="w", padx=10, pady=(8, 0))
list_frame = tk.Frame(file_frame, bg=BG_INPUT, bd=0)
list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
scrollbar = tk.Scrollbar(list_frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.file_listbox = tk.Listbox(list_frame, bg=BG_INPUT, fg=WHITE,
selectbackground=RED, selectforeground=WHITE,
font=("Consolas", 11), bd=0, highlightthickness=0,
yscrollcommand=scrollbar.set)
self.file_listbox.pack(fill=tk.BOTH, expand=True)
scrollbar.config(command=self.file_listbox.yview)
# Buttons
btn_frame = tk.Frame(file_frame, bg=BG_CARD)
btn_frame.pack(fill=tk.X, padx=10, pady=5)
self._make_btn(btn_frame, "βž• Add Files", self._add_files, RED).pack(side=tk.LEFT, padx=3)
self._make_btn(btn_frame, "πŸ“‚ Add Folder", self._add_folder, RED_DARK).pack(side=tk.LEFT, padx=3)
self._make_btn(btn_frame, "❌ Remove", self._remove, "#444444").pack(side=tk.LEFT, padx=3)
self._make_btn(btn_frame, "πŸ—‘οΈ Clear All", self._clear, "#444444").pack(side=tk.LEFT, padx=3)
# ─── Settings ───────────────────────────────────────────
settings = tk.Frame(self.root, bg=BG_CARD, bd=0)
settings.pack(fill=tk.X, padx=10, pady=5)
tk.Label(settings, text="βš™οΈ Settings",
font=("Arial", 13, "bold"), fg=RED, bg=BG_CARD).pack(anchor="w", padx=10, pady=(8, 0))
grid = tk.Frame(settings, bg=BG_CARD)
grid.pack(fill=tk.X, padx=10, pady=5)
# Language
tk.Label(grid, text="Language:", fg=GRAY, bg=BG_CARD).grid(row=0, column=0, sticky="w", padx=5, pady=3)
self.lang_var = tk.StringVar(value="eng+fas")
tk.Entry(grid, textvariable=self.lang_var, width=15, bg=BG_INPUT, fg=WHITE,
insertbackground=WHITE, bd=0).grid(row=0, column=1, sticky="w", padx=5, pady=3)
# DPI
tk.Label(grid, text="DPI:", fg=GRAY, bg=BG_CARD).grid(row=0, column=2, sticky="w", padx=15, pady=3)
self.dpi_var = tk.StringVar(value="300")
tk.Entry(grid, textvariable=self.dpi_var, width=8, bg=BG_INPUT, fg=WHITE,
insertbackground=WHITE, bd=0).grid(row=0, column=3, sticky="w", padx=5, pady=3)
# Title
tk.Label(grid, text="Title:", fg=GRAY, bg=BG_CARD).grid(row=1, column=0, sticky="w", padx=5, pady=3)
self.title_var = tk.StringVar(value="Scan2Doc Output")
tk.Entry(grid, textvariable=self.title_var, width=15, bg=BG_INPUT, fg=WHITE,
insertbackground=WHITE, bd=0).grid(row=1, column=1, sticky="w", padx=5, pady=3)
# Font
tk.Label(grid, text="Font:", fg=GRAY, bg=BG_CARD).grid(row=1, column=2, sticky="w", padx=15, pady=3)
self.font_var = tk.StringVar(value="B Nazanin")
tk.Entry(grid, textvariable=self.font_var, width=15, bg=BG_INPUT, fg=WHITE,
insertbackground=WHITE, bd=0).grid(row=1, column=3, sticky="w", padx=5, pady=3)
# ─── Progress ───────────────────────────────────────────
prog_frame = tk.Frame(self.root, bg=BG_CARD, bd=0)
prog_frame.pack(fill=tk.X, padx=10, pady=5)
self.progress_label = tk.Label(prog_frame, text="Ready",
font=("Arial", 12), fg=GRAY, bg=BG_CARD)
self.progress_label.pack(anchor="w", padx=10, pady=(5, 0))
style = ttk.Style()
style.theme_use("default")
style.configure("Red.Horizontal.TProgressbar",
troughcolor=BG_INPUT, background=RED, borderwidth=0)
self.progress_bar = ttk.Progressbar(prog_frame, style="Red.Horizontal.TProgressbar",
length=400, mode="determinate")
self.progress_bar.pack(fill=tk.X, padx=10, pady=5)
# ─── Log ────────────────────────────────────────────────
log_frame = tk.Frame(self.root, bg=BG_CARD, bd=0)
log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
tk.Label(log_frame, text="πŸ“‹ Log",
font=("Arial", 13, "bold"), fg=RED, bg=BG_CARD).pack(anchor="w", padx=10, pady=(8, 0))
log_inner = tk.Frame(log_frame, bg=BG_INPUT, bd=0)
log_inner.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
log_scroll = tk.Scrollbar(log_inner)
log_scroll.pack(side=tk.RIGHT, fill=tk.Y)
self.log_text = tk.Text(log_inner, bg=BG_INPUT, fg=WHITE, font=("Consolas", 11),
bd=0, highlightthickness=0, wrap=tk.WORD,
yscrollcommand=log_scroll.set)
self.log_text.pack(fill=tk.BOTH, expand=True)
log_scroll.config(command=self.log_text.yview)
# ─── Bottom ─────────────────────────────────────────────
bottom = tk.Frame(self.root, bg=BG_DARK)
bottom.pack(fill=tk.X, padx=10, pady=5)
tk.Label(bottom, text="⚑ Created by Us.Abolfazl",
font=("Arial", 10), fg=RED, bg=BG_DARK).pack(side=tk.LEFT, padx=10)
self.process_btn = tk.Button(bottom, text="πŸš€ Process", width=18,
font=("Arial", 14, "bold"),
bg=RED, fg=WHITE, activebackground=RED_HOVER,
activeforeground=WHITE, bd=0, cursor="hand2",
command=self._start_processing)
self.process_btn.pack(side=tk.RIGHT, padx=10)
def _make_btn(self, parent, text, cmd, bg_color):
return tk.Button(parent, text=text, command=cmd, bg=bg_color, fg=WHITE,
activebackground=RED_HOVER, activeforeground=WHITE,
bd=0, font=("Arial", 10), cursor="hand2", padx=8, pady=4)
def _add_files(self):
filetypes = [
("All supported", " ".join(f"*{e}" for e in SUPPORTED_EXTS)),
("Images", "*.jpg *.jpeg *.png *.tiff *.tif *.bmp *.gif *.webp"),
("PDFs", "*.pdf"),
]
paths = filedialog.askopenfilenames(filetypes=filetypes)
for p in paths:
if p not in self.files:
self.files.append(p)
self.file_listbox.insert(tk.END, p)
self._update_label()
def _add_folder(self):
folder = filedialog.askdirectory()
if not folder:
return
for f in sorted(os.listdir(folder)):
ext = os.path.splitext(f)[1].lower()
if ext in SUPPORTED_EXTS:
full = os.path.join(folder, f)
if full not in self.files:
self.files.append(full)
self.file_listbox.insert(tk.END, full)
self._update_label()
def _remove(self):
sel = self.file_listbox.curselection()
for idx in reversed(sel):
self.file_listbox.delete(idx)
del self.files[idx]
self._update_label()
def _clear(self):
self.files.clear()
self.file_listbox.delete(0, tk.END)
self._update_label()
def _update_label(self):
self.progress_label.config(text=f"Files: {len(self.files)}")
def _log(self, msg):
self.root.after(0, lambda: (
self.log_text.insert(tk.END, msg + "\n"),
self.log_text.see(tk.END)
))
def _start_processing(self):
if self.processing:
messagebox.showinfo("Info", "Processing is already running.")
return
if not self.files:
messagebox.showwarning("Warning", "No files to process. Add files first.")
return
output_path = filedialog.asksaveasfilename(
defaultextension=".docx",
filetypes=[("Word Document", "*.docx")],
initialfile="output.docx"
)
if not output_path:
return
self.processing = True
self.process_btn.config(state="disabled", bg=DIM)
self.log_text.delete("1.0", tk.END)
lang = self.lang_var.get()
title = self.title_var.get()
font = self.font_var.get()
try:
dpi = int(self.dpi_var.get())
except ValueError:
dpi = 300
def worker():
try:
self._log("⚑ Starting...")
# Process each file
for idx, fpath in enumerate(self.files):
self._log(f" [{idx+1}/{len(self.files)}] {os.path.basename(fpath)}")
out = os.path.join(os.path.dirname(output_path) or ".",
f"output_{idx+1}.docx") if len(self.files) > 1 else output_path
success = process_file(fpath, out, lang, title, font, dpi)
if success:
self._log(f" βœ… Saved: {out}")
else:
self._log(f" ❌ Failed: {fpath}")
self._log(f"\nβœ… All done! Check: {output_path}")
self.root.after(0, lambda: self.progress_label.config(text="Done!", fg=GREEN))
except Exception as e:
import traceback
self._log(f"❌ Error: {e}")
self._log(traceback.format_exc())
self.root.after(0, lambda: self.progress_label.config(text="Error!", fg="#ff1744"))
finally:
self.processing = False
self.root.after(0, lambda: self.process_btn.config(state="normal", bg=RED))
threading.Thread(target=worker, daemon=True).start()
def run(self):
self.root.mainloop()
def main():
gui = Scan2DocGUI()
gui.run()
if __name__ == "__main__":
main()