From 7edbf9f2005eb8afc247b077bee41238796722e4 Mon Sep 17 00:00:00 2001 From: "(Tim) Efthimis Kritikos" Date: Wed, 28 May 2025 23:30:22 +0100 Subject: [PATCH] Added event timeline, background color setting and fixed file writing --- metadata_writer.py | 104 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 82 insertions(+), 22 deletions(-) diff --git a/metadata_writer.py b/metadata_writer.py index 610001c..b2d58e6 100755 --- a/metadata_writer.py +++ b/metadata_writer.py @@ -22,12 +22,20 @@ import sys import json import tkinter as tk import hashlib + +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,NavigationToolbar2Tk) +from datetime import datetime + from time import strftime, localtime from tkinter import messagebox from tkinter import Frame from tkcalendar import Calendar from PIL import Image, ImageTk +background_color='#DDDDDD' + #Got TextScrollCombo from stack overflow https://stackoverflow.com/questions/13832720/how-to-attach-a-scrollbar-to-a-text-widget class TextScrollCombo(tk.Frame): @@ -42,13 +50,15 @@ class TextScrollCombo(tk.Frame): self.grid_columnconfigure(0, weight=1) # create a Text widget - self.txt = tk.Text(self,height=10) + self.txt = tk.Text(self,height=10,bg=background_color) self.txt.grid(row=0, column=0, sticky="nsew", padx=2, pady=2) # create a Scrollbar and associate it with txt - scrollb = tk.Scrollbar(self, command=self.txt.yview) + scrollb = tk.Scrollbar(self, command=self.txt.yview,bg=background_color) scrollb.grid(row=0, column=1, sticky='nsew') self.txt['yscrollcommand'] = scrollb.set + self.configure(background=background_color) + def get(c,a,b): return c.txt.get(a,b) @@ -58,9 +68,11 @@ class TitledEntry(tk.Frame): super().__init__(root_window) - title_entry = tk.Entry(self,state=input_state,textvariable=tk.StringVar(value=init_text)) - tk.Label(self, text=text).pack(side=tk.LEFT) - title_entry.pack(fill=tk.X) + self.title_entry = tk.Entry(self,state=input_state,textvariable=tk.StringVar(value=init_text),bg=background_color) + tk.Label(self, text=text, bg=background_color).pack(side=tk.LEFT) + self.title_entry.pack(fill=tk.X) + def get(c): + return c.title_entry.get() #Got md5Checksum from someones blog https://www.joelverhagen.com/blog/2011/02/md5-hash-of-file-in-python/ def md5Checksum(filePath): @@ -73,6 +85,47 @@ def md5Checksum(filePath): m.update(data) return m.hexdigest() + +def event_timeline(window,events): + plot_line_width=0.8 + + fig, ax = plt.subplots(figsize=(12, 1.8), constrained_layout=True) + ax.set_facecolor('none') # Comment out to debug out of bound graph + fig.patch.set_facecolor('none') + ax.set_position([.01,0,0.8,1]) + + timelines=[] + labels=[] + for item in events: + labels.append(item["text"]) + timelines.append(datetime.fromtimestamp(item["time"])) + + offsets= [3,2,1] + levels = np.tile(offsets, int(np.ceil(len(timelines)/len(offsets))))[:len(timelines)] + + ax.axhline(0, c="black",linewidth=2) + ax.vlines(timelines, 0, levels, color='black',linewidth=plot_line_width ) #Draw event lines + ax.plot(timelines, np.zeros_like(timelines), "-o", color="k", markerfacecolor="w",linewidth=plot_line_width ) #Draw the line with the points + + for t, l, b in zip(timelines, levels, labels): + ax.annotate(b+"\n"+t.strftime("%d/%m/%Y"), xy=(t, l), + xytext=(0, -20), textcoords='offset points', + horizontalalignment='left', + verticalalignment='bottom' if l > 0 else 'top', + color='black', + fontsize=9,bbox=dict(facecolor=background_color, edgecolor='black', boxstyle='round,pad=.5', linewidth=plot_line_width) + ) + + ax.yaxis.set_visible(False) + ax.spines[["left", "top", "right"]].set_visible(False) + ax.spines['bottom'].set_position(('data', -8000)) + + canvas = FigureCanvasTkAgg(fig, master = window) + canvas.draw() + + return canvas.get_tk_widget() + + def main(image_path): data = { @@ -81,20 +134,21 @@ def main(image_path): "capture_time_start": 0, "capture_time_end": 0, "image_sha512": md5Checksum(image_path), - "description": "" + "description": "", + "events" : [{ "time": 1733055790, "text": "Data captured" }, + { "time": 1741745288, "text": "Raw file developed"}, + { "time": 1747012088, "text": "Metadata written" }, + { "time": 1747876088, "text": "Metadata modified" }, + { "time": 1759876088, "text": "Metadata version updated" } + ] } def save_and_exit(): - title = title_entry.get() - description = description_entry.get("1.0",'end-1c') - data = { - "version": "v0.0-dev", - "title": title, - "capture_time_start": int(time.mktime(time.strptime(timestamp_start.get(), '%Y-%m-%d %H:%M:%S'))), - "capture_time_end": int(time.mktime(time.strptime(timestamp_end.get(), '%Y-%m-%d %H:%M:%S'))), - "image_sha512": md5Checksum(image_path), - "description": description - } + description_value = description_entry.get("1.0",'end-1c') + data["title"] = title.get() + data["capture_time_start"] = int(time.mktime(time.strptime(timestamp_start.get(), '%Y-%m-%d %H:%M:%S'))) + data["capture_time_end"] = int(time.mktime(time.strptime(timestamp_end.get(), '%Y-%m-%d %H:%M:%S'))) + data["description"] = description_value with open("output.json", "w") as f: json.dump(data, f, indent=4) @@ -104,13 +158,14 @@ def main(image_path): # GUI setup root = tk.Tk() root.title("Metadata Writer") + root.configure(background=background_color) # Load and display image img = Image.open(image_path) img.thumbnail((400, 400)) # Resize for display photo = ImageTk.PhotoImage(img) - img_label = tk.Label(root, image=photo) + img_label = tk.Label(root, image=photo, bg=background_color) img_label.image = photo # keep a reference @@ -118,21 +173,23 @@ def main(image_path): time_end=1547517370 timestamp=Frame(root) + timestamp.configure(bg=background_color) start_var = tk.StringVar(value=strftime('%Y-%m-%d %H:%M:%S', localtime(time_start))) end_var = tk.StringVar(value=strftime('%Y-%m-%d %H:%M:%S', localtime(time_end))) timestamp_start = tk.Entry(timestamp,textvariable=start_var) timestamp_end = tk.Entry(timestamp,textvariable=end_var) - tk.Label(timestamp, text="Shot time/date start:").grid(row=0,column=0) - tk.Label(timestamp, text="Shot time/date end:").grid(row=0,column=2) + tk.Label(timestamp, text="Shot time/date start:", bg=background_color).grid(row=0,column=0) + tk.Label(timestamp, text="Shot time/date end:",bg=background_color).grid(row=0,column=2) timestamp_start.grid(row=0,column=1) timestamp_end.grid(row=0,column=3) # Input fields description=Frame(root) - tk.Label(description, text="Description:").pack(side=tk.LEFT) + tk.Label(description, text="Description:",bg=background_color).pack(side=tk.LEFT) description_entry = TextScrollCombo(description) description_entry.pack() + description.configure(bg=background_color) description_entry.config(width=600, height=100) @@ -140,9 +197,11 @@ def main(image_path): sha512sum=TitledEntry(root,"Image SHA512",tk.DISABLED,data["image_sha512"]) version=TitledEntry(root,"Version",tk.DISABLED,data["version"]) - # Save button - save_button = tk.Button(root, text="Save and Exit", command=save_and_exit) + timeline = event_timeline(root,data["events"]) + timeline.configure(bg=background_color) + # Save button + save_button = tk.Button(root, text="Save and Exit", command=save_and_exit, bg=background_color) img_label .grid(row=0,column=0,rowspan=6,sticky='n') title .grid(row=0,column=1,sticky="we") @@ -151,6 +210,7 @@ def main(image_path): sha512sum .grid(row=3,column=1,sticky="we") version .grid(row=4,column=1,sticky="we") save_button .grid(row=5,column=1) + timeline .grid(row=6,column=0,columnspan=2) root.mainloop()