NIce, it still keeps empty folders tho, id delete them instead of keeping them.Maybe as last pass to check if theres files inside all folders after all the checks and if not - boot the folder.
Also i have this small tool for checking all paths inside txt file, and you have options to delete the line or edit it, it was useful for me , maybe will be for someone else, feel free to do whatever with it , i know you have check orphans too but we do not have ability to delete of edit on the fly as we detect them so i had to do it.
As usual you save this code into txt file , rename it to something like txtcleaner.bat and run it :

Also i have this small tool for checking all paths inside txt file, and you have options to delete the line or edit it, it was useful for me , maybe will be for someone else, feel free to do whatever with it , i know you have check orphans too but we do not have ability to delete of edit on the fly as we detect them so i had to do it.
As usual you save this code into txt file , rename it to something like txtcleaner.bat and run it :
Code:
0<0# : ^
'''
@echo off
set script=%~f0
python -x "%script%" %*
exit /b 0/b 0
'''
import tkinter as tk
from tkinter import filedialog, messagebox
import os
import fnmatch
class TextFileScannerApp:
def __init__(self, master):
self.master = master
master.title("TXTCLEANER")
self.frame = tk.Frame(master)
self.frame.pack(fill="both", expand=True)
self.text = tk.Text(self.frame, wrap="word", width=50, height=20)
self.text.pack(side="left", fill="both", expand=True)
self.scrollbar = tk.Scrollbar(self.frame, command=self.text.yview)
self.scrollbar.pack(side="right", fill="y")
self.text.config(yscrollcommand=self.scrollbar.set)
self.button_frame = tk.Frame(master)
self.button_frame.pack(side="right", fill="y")
self.save_button = tk.Button(self.button_frame, text="Save TXT", command=self.save_file)
self.save_button.pack(pady=5)
self.load_button = tk.Button(master, text="Load TXT", command=self.load_file)
self.load_button.pack(side="left", padx=5, pady=5)
self.scan_button = tk.Button(master, text="Scan for missing files", command=self.scan_file)
self.scan_button.pack()
self.text_file_path = None
def load_file(self):
file_path = filedialog.askopenfilename(filetypes=[("Text files", "*.txt")])
if file_path:
self.text_file_path = file_path
with open(file_path, "r") as file:
self.text.delete("1.0", tk.END)
self.text.insert(tk.END, file.read())
def save_file(self):
if not self.text_file_path:
messagebox.showwarning("No File Loaded", "Please load a text file first.")
return
with open(self.text_file_path, "w") as file:
file.write(self.text.get("1.0", tk.END))
self.show_save_dialog()
def scan_file(self):
if not self.text_file_path:
messagebox.showwarning("No File Loaded", "Please load a text file first.")
return
data_folder_index = self.text_file_path.rfind("data/")
if data_folder_index == -1:
messagebox.showwarning("Invalid File Path", "The loaded file is not inside a 'data/' folder.")
return
data_folder_path = self.text_file_path[:data_folder_index]
content = self.text.get("1.0", tk.END)
lines = content.split("\n")
for i, line in enumerate(lines, start=1):
if "data/" in line:
path_with_extension = line[line.find("data/"):]
path, extension = os.path.splitext(path_with_extension.strip())
if not path.startswith("data/"):
continue
extension = extension[:4]
full_path = os.path.join(data_folder_path, path) + extension
print("Checking path:", full_path)
if not self.file_exists_case_insensitive(full_path):
self.show_error_dialog(i, full_path)
return
self.text.tag_remove("highlight", "1.0", tk.END)
print("ALL PATHS ARE GOOD!")
def file_exists_case_insensitive(self, path):
directory, filename = os.path.split(path)
for file in os.listdir(directory):
if fnmatch.fnmatch(file, filename):
return True
return False
def show_error_dialog(self, line_number, full_path):
dialog = tk.Toplevel(self.master)
dialog.title("File Not Found")
label = tk.Label(dialog, text=f"File not found on disk: {full_path}")
label.pack(padx=20, pady=10)
button_frame = tk.Frame(dialog)
button_frame.pack(pady=10)
def delete_line():
content = self.text.get("1.0", tk.END).split("\n")
del content[line_number - 1]
self.text.delete("1.0", tk.END)
self.text.insert("1.0", "\n".join(content))
dialog.destroy()
self.scan_file() # Continue scanning
delete_button = tk.Button(button_frame, text="DELETE LINE", command=delete_line)
delete_button.pack(side="left", padx=10)
ok_button = tk.Button(button_frame, text="EDIT LINE", command=dialog.destroy)
ok_button.pack(side="left", padx=10)
self.text.tag_add("highlight", f"{line_number}.0", f"{line_number}.end")
self.text.tag_config("highlight", background="yellow")
self.text.mark_set("insert", f"{line_number}.0")
self.text.see(f"{line_number}.0")
self.master.update_idletasks()
master_width = self.master.winfo_width()
master_height = self.master.winfo_height()
master_x = self.master.winfo_x()
master_y = self.master.winfo_y()
dialog_width = dialog.winfo_reqwidth()
dialog_height = dialog.winfo_reqheight()
position_right = int(master_x + (master_width / 2) - (dialog_width / 2))
position_down = int(master_y + (master_height / 2) - (dialog_height / 2) - (master_height / 6)) # slightly higher
dialog.geometry(f"+{position_right}+{position_down}")
def show_save_dialog(self):
dialog = tk.Toplevel(self.master)
dialog.title("File Saved")
label = tk.Label(dialog, text="The file has been saved successfully.")
label.pack(padx=20, pady=10)
ok_button = tk.Button(dialog, text="OK", command=dialog.destroy)
ok_button.pack(pady=10)
self.master.update_idletasks()
master_width = self.master.winfo_width()
master_height = self.master.winfo_height()
master_x = self.master.winfo_x()
master_y = self.master.winfo_y()
dialog_width = dialog.winfo_reqwidth()
dialog_height = dialog.winfo_reqheight()
position_right = int(master_x + (master_width / 2) - (dialog_width / 2))
position_down = int(master_y + (master_height / 2) - (dialog_height / 2) - (master_height / 3)) # slightly higher
dialog.geometry(f"+{position_right}+{position_down}")
root = tk.Tk()
root.tk.call('tk', 'scaling', 3.0)
app = TextFileScannerApp(root)
root.mainloop()

Last edited: