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

Popular posts from this blog

πŸš€ Simple Login & Registration System in Python Tkinter πŸ“±

πŸš€ Create a Python Screen Recorder with Audio (Complete Code)

πŸ“‘ Fuzzu Packet Sniffer – Python GUI for Real-Time IP Monitoring | Tkinter + Scapy