File Metadata Viewer — Python Tkinter App to Inspect Files (SHA256, EXIF, Export JSON) — FuzzuTech

 Demo :


Click Video πŸ‘‡πŸ‘‡πŸ‘‡






























Description :


File Metadata Viewer — Inspect any file with a modern Tkinter GUI: SHA256, MIME, EXIF, preview & export JSON. Python + Pillow + pyperclip demo by FuzzuTech.


Features :

  • Modern dark Tkinter GUI for quick file inspection

  • Shows file name, full path, human-readable size

  • Displays created / modified / accessed timestamps

  • Detects MIME type & extension

  • Computes SHA256 hash (chunked reading)

  • EXIF extraction & image preview (Pillow)

  • Export metadata as pretty JSON

  • Copy values to clipboard (pyperclip)

  • Lightweight, single-file demo (main.py) + sample assets


Code :


"""

FileMetadataViewer - Modern Tkinter GUI

Author: Fuzzu Developer (add your name)

Run: python main.py

Requires: pillow, pyperclip

"""


import os

import sys

import json

import mimetypes

import hashlib

import platform

from datetime import datetime

import tkinter as tk

from tkinter import ttk, filedialog, messagebox

from tkinter.scrolledtext import ScrolledText

from PIL import Image, ImageTk, ExifTags

import pyperclip


# ---------- Helpers ----------

def format_bytes(size):

    # simple human readable

    for unit in ['B','KB','MB','GB','TB']:

        if size < 1024.0:

            return f"{size:.2f} {unit}"

        size /= 1024.0

    return f"{size:.2f} PB"


def sha256sum(path, chunk_size=8192):

    h = hashlib.sha256()

    with open(path, 'rb') as f:

        for block in iter(lambda: f.read(chunk_size), b''):

            h.update(block)

    return h.hexdigest()


def file_times(path):

    s = os.stat(path)

    # Note: on some systems ctime is change-time

    return {

        "created": datetime.fromtimestamp(getattr(s, 'st_ctime')).strftime("%Y-%m-%d %H:%M:%S"),

        "modified": datetime.fromtimestamp(s.st_mtime).strftime("%Y-%m-%d %H:%M:%S"),

        "accessed": datetime.fromtimestamp(s.st_atime).strftime("%Y-%m-%d %H:%M:%S"),

    }


def get_mime(path):

    mime, _ = mimetypes.guess_type(path)

    return mime or "Unknown"


def get_exif(path):

    try:

        img = Image.open(path)

        exif_raw = img._getexif()

        if not exif_raw:

            return {}

        exif = {}

        for tag_id, value in exif_raw.items():

            tag = ExifTags.TAGS.get(tag_id, tag_id)

            exif[tag] = value

        return exif

    except Exception:

        return {}


# ---------- GUI ----------

class App(ttk.Frame):

    def __init__(self, root):

        super().__init__(root)

        self.root = root

        self.setup_root()

        self.create_styles()

        self.create_widgets()

        self.pack(fill="both", expand=True)


        self.current_image_tk = None

        self.current_file = None

        self.metadata = {}


    def setup_root(self):

        self.root.title("File Metadata Viewer — Fuzzu Developer")

        try:

            self.root.iconphoto(False, tk.PhotoImage(file=os.path.join("assets","icon.png")))

        except Exception:

            pass

        self.root.geometry("820x520")

        # make window responsive

        self.root.minsize(780, 480)


    def create_styles(self):

        style = ttk.Style()

        # Use default theme but tweak

        style.theme_use("clam")

        style.configure("TFrame", background="#0f1720")

        style.configure("TLabel", background="#0f1720", foreground="white", font=("Inter", 11))

        style.configure("Header.TLabel", font=("Inter", 16, "bold"), foreground="#FFD166")

        style.configure("Accent.TButton", background="#FF6B6B", foreground="white", font=("Inter", 11, "bold"))

        style.map("Accent.TButton",

                  background=[("active", "#FF4B4B")])

        style.configure("Treeview", background="#0b1220", fieldbackground="#0b1220", foreground="white")

        style.configure("TEntry", fieldbackground="#0b1220", foreground="white")


    def create_widgets(self):

        # top bar

        top = ttk.Frame(self)

        top.pack(fill="x", padx=12, pady=(12,6))


        title = ttk.Label(top, text="File Metadata Viewer", style="Header.TLabel")

        title.pack(side="left")


        btn_frame = ttk.Frame(top)

        btn_frame.pack(side="right")


        select_btn = ttk.Button(btn_frame, text="Select File", style="Accent.TButton", command=self.select_file)

        select_btn.pack(side="left", padx=6)


        export_btn = ttk.Button(btn_frame, text="Export JSON", command=self.export_json)

        export_btn.pack(side="left", padx=6)


        copy_btn = ttk.Button(btn_frame, text="Copy Selected", command=self.copy_selected)

        copy_btn.pack(side="left", padx=6)


        # main area

        main = ttk.Frame(self)

        main.pack(fill="both", expand=True, padx=12, pady=8)


        # left: metadata tree / details

        left = ttk.Frame(main)

        left.pack(side="left", fill="both", expand=True)


        # info tree

        self.tree = ttk.Treeview(left, columns=("property", "value"), show="headings", selectmode="browse", height=18)

        self.tree.heading("property", text="Property")

        self.tree.heading("value", text="Value")

        self.tree.column("property", width=200, anchor="w")

        self.tree.column("value", anchor="w")

        self.tree.pack(fill="both", expand=True, pady=(0,8))


        # small details row

        details_frame = ttk.Frame(left)

        details_frame.pack(fill="x", pady=(0,6))


        self.label_path = ttk.Label(details_frame, text="Path: —", wraplength=380)

        self.label_path.pack(anchor="w")


        self.label_size = ttk.Label(details_frame, text="Size: —")

        self.label_size.pack(side="left", padx=(0,8))


        self.label_mime = ttk.Label(details_frame, text="MIME: —")

        self.label_mime.pack(side="left")


        # right: preview + raw metadata

        right = ttk.Frame(main, width=320)

        right.pack(side="right", fill="y", padx=(8,0))


        preview_lbl = ttk.Label(right, text="Preview", font=("Inter", 12, "bold"))

        preview_lbl.pack(anchor="center", pady=(6,4))


        self.canvas = tk.Label(right, background="#0f1720")

        self.canvas.pack(ipadx=10, ipady=10, pady=(0,8))


        exif_lbl = ttk.Label(right, text="Raw / EXIF", font=("Inter", 12, "bold"))

        exif_lbl.pack(anchor="w", padx=6)


        self.raw = ScrolledText(right, height=10, wrap="none", background="#071018", foreground="white")

        self.raw.pack(fill="both", expand=True, padx=6, pady=(4,0))


        # status bar

        self.status = ttk.Label(self, text="Ready", anchor="w")

        self.status.pack(fill="x", side="bottom", padx=12, pady=(6,12))


        # bindings

        self.tree.bind("<Double-1>", self.on_tree_double)

        self.tree.bind("<<TreeviewSelect>>", lambda e: self.on_select())


    # ---------- Actions ----------

    def set_status(self, txt):

        self.status.config(text=txt)


    def select_file(self):

        path = filedialog.askopenfilename()

        if not path:

            return

        self.load_file(path)


    def load_file(self, path):

        self.current_file = path

        self.set_status("Loading metadata...")

        self.tree.delete(*self.tree.get_children())

        try:

            meta = {}

            meta["File Name"] = os.path.basename(path)

            meta["File Path"] = os.path.abspath(path)

            size = os.path.getsize(path)

            meta["Size"] = format_bytes(size)

            meta.update(file_times(path))

            meta["MIME Type"] = get_mime(path)

            meta["Extension"] = os.path.splitext(path)[1] or "—"

            meta["SHA256"] = sha256sum(path)

            meta["Platform"] = platform.system()

            meta["Python Version"] = platform.python_version()


            # put into tree

            for k, v in meta.items():

                self.tree.insert("", "end", values=(k, v))


            # update small labels

            self.label_path.config(text=f"Path: {meta['File Path']}")

            self.label_size.config(text=f"Size: {meta['Size']}")

            self.label_mime.config(text=f"MIME: {meta['MIME Type']}")


            # EXIF for images

            exif = {}

            if meta["MIME Type"] and meta["MIME Type"].startswith("image"):

                exif = get_exif(path)

                # show preview

                self.show_image_preview(path)

            else:

                # clear preview

                self.canvas.config(image="", text="No preview", compound="center", width=280, height=180, fg="white")

                self.current_image_tk = None


            # raw text

            raw_data = {

                "metadata": meta,

                "exif": exif

            }

            pretty = json.dumps(raw_data, indent=2, default=str)

            self.raw.delete("1.0", tk.END)

            self.raw.insert(tk.END, pretty)


            # store

            self.metadata = raw_data

            self.set_status("Metadata loaded")

        except Exception as e:

            messagebox.showerror("Error", f"Failed to load file: {e}")

            self.set_status("Error loading file")


    def show_image_preview(self, path):

        try:

            img = Image.open(path)

            # maintain aspect but fit into box

            max_w, max_h = 280, 180

            img.thumbnail((max_w, max_h), Image.ANTIALIAS)

            self.current_image_tk = ImageTk.PhotoImage(img)

            self.canvas.config(image=self.current_image_tk, text="")

        except Exception as e:

            self.canvas.config(text="Preview error", image="")

            self.current_image_tk = None


    def export_json(self):

        if not self.current_file or not self.metadata:

            messagebox.showinfo("No file", "Please load a file first.")

            return

        path = filedialog.asksaveasfilename(defaultextension=".json", filetypes=[("JSON file","*.json")])

        if not path:

            return

        try:

            with open(path, "w", encoding="utf-8") as f:

                json.dump(self.metadata, f, indent=2, default=str)

            self.set_status(f"Exported JSON → {path}")

            messagebox.showinfo("Exported", f"Metadata exported to:\n{path}")

        except Exception as e:

            messagebox.showerror("Error", f"Failed to export: {e}")


    def copy_selected(self):

        sel = self.tree.selection()

        if not sel:

            messagebox.showinfo("Select", "Select a property row to copy its value.")

            return

        val = self.tree.item(sel[0], 'values')[1]

        pyperclip.copy(str(val))

        self.set_status("Copied to clipboard")


    def on_tree_double(self, event):

        sel = self.tree.selection()

        if not sel:

            return

        prop, val = self.tree.item(sel[0], 'values')

        # show a small dialog with full value and copy button

        dlg = tk.Toplevel(self.root)

        dlg.title(prop)

        dlg.geometry("500x220")

        dlg.configure(bg="#0b1220")

        lbl = ttk.Label(dlg, text=prop, font=("Inter", 12, "bold"))

        lbl.pack(pady=(10,6))

        txt = ScrolledText(dlg, height=6, wrap="word", background="#071018", foreground="white")

        txt.insert("1.0", str(val))

        txt.pack(fill="both", expand=True, padx=10, pady=(0,6))

        def do_copy():

            pyperclip.copy(str(val))

            messagebox.showinfo("Copied", "Value copied to clipboard.")

        btn = ttk.Button(dlg, text="Copy", command=do_copy)

        btn.pack(pady=(0,10))

        dlg.transient(self.root)

        dlg.grab_set()

        dlg.wait_window()


    def on_select(self):

        # update status with selected property

        sel = self.tree.selection()

        if sel:

            prop, val = self.tree.item(sel[0], 'values')

            self.set_status(f"Selected: {prop}")


# ---------- Run ----------

def main():

    root = tk.Tk()

    # prefer dark background for the whole root

    root.configure(bg="#0f1720")

    app = App(root)

    root.mainloop()


if __name__ == "__main__":

    main()

Comments

Popular posts from this blog

Is This News Real or Fake? πŸ€– AI Exposes the Truth | FuzzuTech Python App Demo

🚨 Python Intrusion Detection System (IDS) – Real-Time ML + Tkinter GUI Project | FuzzuTech

Educational File Encryptor GUI (Python AES Project) | FuzzuTech