Auto Screenshot Logger GUI in Python | FuzzuTech
Demo :
Click Video πππ
Features (SEO):
-
Python screenshot capture tool
-
Auto screenshot logger with GUI
-
Productivity automation with Python
-
Best Python projects for beginners
Code :
"""
Auto Screenshot Logger (single-file)
- Modern GUI using CustomTkinter (CTk). Falls back to Tkinter if CTk not installed.
- Uses mss for cross-platform high-speed screenshots.
- Features:
* Start / Stop automatic screenshot capturing
* Set interval (seconds)
* Choose output folder
* Live preview of latest screenshot
* Show saved count and log messages
* Optional retention days (auto-delete older screenshots)
- Save filenames as: screenshot_YYYYMMDD_HHMMSS.png
"""
import os
import time
import threading
from datetime import datetime, timedelta
from queue import Queue, Empty
# Try imports
try:
import customtkinter as ctk
from tkinter import filedialog, messagebox
CTK = True
except Exception:
import tkinter as ctk # fallback names, will use tkinter widgets differently
from tkinter import filedialog, messagebox
CTK = False
try:
from PIL import Image, ImageTk
PIL_AVAILABLE = True
except Exception:
PIL_AVAILABLE = False
try:
import mss
import mss.tools
except Exception as e:
raise SystemExit("Please install required package 'mss' (pip install mss).") from e
# ---------------------------
# Utility functions
# ---------------------------
def ensure_dir(path):
if not os.path.exists(path):
os.makedirs(path, exist_ok=True)
def timestamp_filename(prefix="screenshot", ext="png"):
return f"{prefix}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.{ext}"
# ---------------------------
# Screenshot capture worker
# ---------------------------
class ScreenshotWorker(threading.Thread):
def __init__(self, interval_sec, out_folder, queue, retention_days=None, running_event=None):
super().__init__(daemon=True)
self.interval = max(1, float(interval_sec))
self.out_folder = out_folder
self.queue = queue
self.retention_days = retention_days
self.running_event = running_event or threading.Event()
self._stop_event = threading.Event()
def stop(self):
self._stop_event.set()
def run(self):
ensure_dir(self.out_folder)
with mss.mss() as sct:
while not self._stop_event.is_set():
if not self.running_event.is_set():
# if paused, just wait a bit and continue
time.sleep(0.2)
continue
try:
ts = timestamp_filename("screenshot", "png")
out_path = os.path.join(self.out_folder, ts)
monitor = sct.monitors[0] # full screen
sct_img = sct.grab(monitor)
mss.tools.to_png(sct_img.rgb, sct_img.size, output=out_path)
self.queue.put(("saved", out_path))
# handle retention
if self.retention_days is not None:
try:
cutoff = datetime.now() - timedelta(days=int(self.retention_days))
for fname in os.listdir(self.out_folder):
if not fname.lower().endswith((".png", ".jpg", ".jpeg")):
continue
path = os.path.join(self.out_folder, fname)
mtime = datetime.fromtimestamp(os.path.getmtime(path))
if mtime < cutoff:
os.remove(path)
self.queue.put(("deleted", path))
except Exception as e:
self.queue.put(("error", f"Retention cleanup error: {e}"))
except Exception as e:
self.queue.put(("error", f"Capture error: {e}"))
# wait interval but allow early stop
slept = 0.0
while slept < self.interval and not self._stop_event.is_set():
time.sleep(0.2)
slept += 0.2
# ---------------------------
# GUI app
# ---------------------------
class AutoScreenshotApp:
def __init__(self):
self.queue = Queue()
self.worker = None
self.worker_event = threading.Event()
self.save_folder = os.path.join(os.getcwd(), "screenshots")
ensure_dir(self.save_folder)
self.interval = 10
self.retention_days = None
if CTK and hasattr(ctk, "CTk"): # prefer CTk modern UI
self.root = ctk.CTk()
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("dark-blue")
self._build_ctk_ui()
else:
# fallback simple tkinter UI
import tkinter as tk
from tkinter import ttk
self.root = tk.Tk()
self.root.title("Auto Screenshot Logger (Tkinter fallback)")
self._build_tk_ui()
# periodic queue poll
self.root.after(300, self._process_queue)
# -------------------
# CTk UI
# -------------------
def _build_ctk_ui(self):
self.root.title("Auto Screenshot Logger πΈ — FuzzuTech")
self.root.geometry("580x560")
header = ctk.CTkFrame(self.root, height=70)
header.pack(fill="x")
title = ctk.CTkLabel(header, text="πΈ Auto Screenshot Logger", font=ctk.CTkFont(size=20, weight="bold"))
title.place(relx=0.02, rely=0.2)
content = ctk.CTkFrame(self.root)
content.pack(fill="both", expand=True, padx=12, pady=12)
left = ctk.CTkFrame(content, width=340)
left.pack(side="left", fill="y", padx=(0,10), pady=6)
right = ctk.CTkFrame(content)
right.pack(side="left", fill="both", expand=True, pady=6)
# Left controls
ctk.CTkLabel(left, text="Save Folder:", anchor="w").pack(padx=12, pady=(12,4), fill="x")
self.folder_label = ctk.CTkLabel(left, text=self.save_folder, wraplength=300, anchor="w")
self.folder_label.pack(padx=12, pady=(0,6), fill="x")
ctk.CTkButton(left, text="Choose Folder", command=self.choose_folder).pack(padx=12, pady=(4,8))
ctk.CTkLabel(left, text="Capture Interval (seconds):", anchor="w").pack(padx=12, pady=(8,4), fill="x")
self.interval_var = ctk.IntVar(value=int(self.interval))
self.interval_entry = ctk.CTkEntry(left, textvariable=self.interval_var, width=140)
self.interval_entry.pack(padx=12, pady=(0,8), anchor="w")
ctk.CTkLabel(left, text="Retention (days, optional):", anchor="w").pack(padx=12, pady=(8,4), fill="x")
self.retention_var = ctk.StringVar(value="")
self.retention_entry = ctk.CTkEntry(left, textvariable=self.retention_var, width=140, placeholder_text="e.g. 7")
self.retention_entry.pack(padx=12, pady=(0,8), anchor="w")
self.start_btn = ctk.CTkButton(left, text="▶ Start Capturing", command=self.start_capture)
self.start_btn.pack(padx=12, pady=(10,6), fill="x")
self.stop_btn = ctk.CTkButton(left, text="⏹ Stop", command=self.stop_capture, state="disabled")
self.stop_btn.pack(padx=12, pady=(6,12), fill="x")
ctk.CTkLabel(left, text="Saved Count:", anchor="w").pack(padx=12, pady=(8,4), fill="x")
self.count_label = ctk.CTkLabel(left, text="0")
self.count_label.pack(padx=12, pady=(0,8), anchor="w")
# Log box
ctk.CTkLabel(left, text="Event Log:", anchor="w").pack(padx=12, pady=(8,4), fill="x")
self.log_box = ctk.CTkTextbox(left, height=160, wrap="word")
self.log_box.pack(padx=12, pady=(0,12), fill="both", expand=False)
# Right: Preview + Controls
ctk.CTkLabel(right, text="Latest Screenshot Preview:", anchor="w", font=ctk.CTkFont(size=16, weight="bold")).pack(padx=12, pady=(8,4), anchor="w")
preview_frame = ctk.CTkFrame(right)
preview_frame.pack(fill="both", expand=True, padx=12, pady=(0,8))
# Canvas for image preview (use a label)
self.preview_label = ctk.CTkLabel(preview_frame, text="No screenshot yet", width=480, height=320, anchor="center")
self.preview_label.pack(fill="both", expand=True, padx=12, pady=12)
# quick controls
btn_row = ctk.CTkFrame(right)
btn_row.pack(fill="x", padx=12, pady=(6,12))
ctk.CTkButton(btn_row, text="Open Folder", command=self.open_folder).pack(side="left", padx=6)
ctk.CTkButton(btn_row, text="Clear Log", command=self.clear_log).pack(side="left", padx=6)
# -------------------
# Tk fallback UI
# -------------------
def _build_tk_ui(self):
import tkinter as tk
from tkinter import ttk
self.root.title("Auto Screenshot Logger (fallback)")
self.root.geometry("860x520")
frm = ttk.Frame(self.root, padding=10)
frm.pack(fill="both", expand=True)
ttk.Label(frm, text="Save Folder:").grid(column=0, row=0, sticky="w")
self.folder_label = ttk.Label(frm, text=self.save_folder, wraplength=500)
self.folder_label.grid(column=0, row=1, sticky="w", columnspan=3, pady=(0,6))
ttk.Button(frm, text="Choose Folder", command=self.choose_folder).grid(column=3, row=1, sticky="e")
ttk.Label(frm, text="Interval (seconds):").grid(column=0, row=2, sticky="w", pady=(8,0))
self.interval_var = tk.IntVar(value=int(self.interval))
self.interval_entry = ttk.Entry(frm, textvariable=self.interval_var, width=12)
self.interval_entry.grid(column=0, row=3, sticky="w")
ttk.Label(frm, text="Retention (days):").grid(column=1, row=2, sticky="w", padx=(12,0))
self.retention_var = tk.StringVar(value="")
self.retention_entry = ttk.Entry(frm, textvariable=self.retention_var, width=12)
self.retention_entry.grid(column=1, row=3, sticky="w", padx=(12,0))
self.start_btn = ttk.Button(frm, text="Start Capturing", command=self.start_capture)
self.start_btn.grid(column=0, row=4, pady=(10,0))
self.stop_btn = ttk.Button(frm, text="Stop", command=self.stop_capture, state="disabled")
self.stop_btn.grid(column=1, row=4, pady=(10,0), padx=(8,0))
ttk.Label(frm, text="Saved Count:").grid(column=0, row=5, sticky="w", pady=(8,0))
self.count_label = ttk.Label(frm, text="0")
self.count_label.grid(column=0, row=6, sticky="w")
ttk.Label(frm, text="Preview:").grid(column=0, row=7, sticky="w", pady=(8,0))
self.preview_label = ttk.Label(frm, text="No screenshot yet", anchor="center", width=60)
self.preview_label.grid(column=0, row=8, columnspan=4, sticky="we", pady=(4,8))
ttk.Label(frm, text="Event Log:").grid(column=0, row=9, sticky="w")
self.log_box = tk.Text(frm, height=8)
self.log_box.grid(column=0, row=10, columnspan=4, sticky="we", pady=(4,0))
ttk.Button(frm, text="Open Folder", command=self.open_folder).grid(column=2, row=4, padx=(12,0))
ttk.Button(frm, text="Clear Log", command=self.clear_log).grid(column=3, row=4)
# -------------------
# Actions
# -------------------
def choose_folder(self):
folder = filedialog.askdirectory(initialdir=self.save_folder)
if folder:
self.save_folder = folder
self.folder_label.configure(text=self.save_folder)
def open_folder(self):
try:
if os.name == 'nt':
os.startfile(self.save_folder)
elif os.name == 'posix':
# mac / linux
if sys.platform == 'darwin':
os.system(f'open "{self.save_folder}"')
else:
os.system(f'xdg-open "{self.save_folder}"')
except Exception as e:
messagebox.showerror("Open Folder", f"Cannot open folder: {e}")
def clear_log(self):
try:
self.log_box.delete("0.0", "end") if not CTK else self.log_box.delete("0.0", "end")
except Exception:
# For CTk textbox method is different; using insert overwrite safe approach
try:
self.log_box.delete("1.0", "end")
except Exception:
pass
def start_capture(self):
try:
interval_val = float(self.interval_var.get())
self.interval = max(1, interval_val)
except Exception:
messagebox.showwarning("Invalid Interval", "Please enter a numeric interval (seconds).")
return
rd = self.retention_var.get().strip()
self.retention_days = int(rd) if rd.isdigit() else None
# if existing worker running, just resume
if self.worker and self.worker.is_alive():
self.worker_event.set()
self.log(f"Resumed capturing (interval={self.interval}s).")
else:
self.worker_event.set()
self.worker = ScreenshotWorker(interval_sec=self.interval,
out_folder=self.save_folder,
queue=self.queue,
retention_days=self.retention_days,
running_event=self.worker_event)
self.worker.start()
self.log(f"Started capturing every {self.interval} seconds.")
self.start_btn.configure(state="disabled")
try:
self.stop_btn.configure(state="normal")
except Exception:
self.stop_btn.configure(state="enabled")
def stop_capture(self):
if self.worker and self.worker.is_alive():
self.worker_event.clear() # pause
self.log("Paused capturing.")
self.start_btn.configure(state="normal")
try:
self.stop_btn.configure(state="disabled")
except Exception:
self.stop_btn.configure(state="disabled")
# -------------------
# Queue processing & GUI updates
# -------------------
def _process_queue(self):
updated = False
try:
while True:
item = self.queue.get_nowait()
kind, payload = item
if kind == "saved":
updated = True
self.log(f"Saved: {payload}")
self._update_preview(payload)
elif kind == "deleted":
self.log(f"Deleted old file: {payload}")
elif kind == "error":
self.log(f"Error: {payload}")
else:
self.log(str(payload))
except Empty:
pass
if updated:
try:
count = len([f for f in os.listdir(self.save_folder) if f.lower().endswith((".png",".jpg",".jpeg"))])
except Exception:
count = 0
self.count_label.configure(text=str(count))
self.root.after(300, self._process_queue)
def _update_preview(self, path):
if not PIL_AVAILABLE:
self.log("Pillow not installed — cannot show preview.")
try:
self.preview_label.configure(text=os.path.basename(path))
except Exception:
pass
return
try:
img = Image.open(path)
# resize to preview area
w, h = img.size
max_w, max_h = (640, 360)
scale = min(max_w / w, max_h / h, 1.0)
new_size = (int(w * scale), int(h * scale))
img = img.resize(new_size, Image.LANCZOS)
self.preview_imgtk = ImageTk.PhotoImage(img)
# set on label
try:
self.preview_label.configure(image=self.preview_imgtk, text="")
except Exception:
# fallback for tk label
self.preview_label.config(image=self.preview_imgtk, text="")
except Exception as e:
self.log(f"Preview error: {e}")
def log(self, text):
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
entry = f"[{ts}] {text}\n"
try:
# CTk textbox supports insert
self.log_box.insert("end", entry)
self.log_box.see("end")
except Exception:
try:
self.log_box.insert("end", entry)
self.log_box.see("end")
except Exception:
pass
def run(self):
self.root.mainloop()
# ---------------------------
# Run app
# ---------------------------
if __name__ == "__main__":
app = AutoScreenshotApp()
app.run()
Comments
Post a Comment