|
import tkinter as tk
|
|
from tkinter import filedialog, ttk, messagebox
|
|
import os
|
|
import threading, queue
|
|
from PIL import Image, UnidentifiedImageError
|
|
|
|
|
|
stop_processing = False
|
|
error_messages = []
|
|
selected_files = []
|
|
error_list = []
|
|
|
|
def open_image_error_fix():
|
|
global stop_processing, error_messages, selected_files, status_var, num_files_var, errors_var, progress, q, error_list, error_window, thread_count_var
|
|
|
|
|
|
root = tk.Tk()
|
|
root.title("Image Error Fix")
|
|
|
|
|
|
status_var = tk.StringVar()
|
|
num_files_var = tk.StringVar()
|
|
errors_var = tk.StringVar(value="Errors: 0")
|
|
progress = tk.IntVar()
|
|
thread_count_var = tk.StringVar(value="4")
|
|
q = queue.Queue()
|
|
|
|
def center_window(window):
|
|
window.update_idletasks()
|
|
width = window.winfo_width() + 120
|
|
height = window.winfo_height()
|
|
x = (window.winfo_screenwidth() // 2) - (width // 2)
|
|
y = (window.winfo_screenheight() // 2) - (height // 2)
|
|
window.geometry(f'{width}x{height}+{x}+{y}')
|
|
|
|
def validate_number(P):
|
|
return P.isdigit() or P == ""
|
|
|
|
def validate_thread_count(var):
|
|
value = var.get()
|
|
if value != "":
|
|
try:
|
|
int_value = int(value)
|
|
if int_value <= 0:
|
|
raise ValueError
|
|
except ValueError:
|
|
messagebox.showerror("Invalid Input", "Please enter a valid number of threads.")
|
|
var.set("")
|
|
|
|
def select_files():
|
|
global selected_files
|
|
filetypes = [
|
|
("All Image files", "*.jpg;*.jpeg;*.png;*.gif;*.bmp;*.tiff;*.tif;*.svg;*.webp"),
|
|
("JPEG files", "*.jpg;*.jpeg"),
|
|
("PNG files", "*.png"),
|
|
("GIF files", "*.gif"),
|
|
("BMP files", "*.bmp"),
|
|
("TIFF files", "*.tiff;*.tif"),
|
|
("SVG files", "*.svg"),
|
|
("WEBP files", "*.webp")
|
|
]
|
|
filepaths = filedialog.askopenfilenames(title="Select Image Files", filetypes=filetypes)
|
|
if filepaths:
|
|
selected_files.clear()
|
|
selected_files.extend(filepaths)
|
|
num_files_var.set(f"{len(selected_files)} files selected.")
|
|
|
|
def detect_errors(file_path):
|
|
"""Detect potential errors in an image file."""
|
|
errors = []
|
|
if not os.access(file_path, os.R_OK):
|
|
errors.append("Permission Denied")
|
|
|
|
try:
|
|
with Image.open(file_path) as img:
|
|
img.verify()
|
|
img = Image.open(file_path)
|
|
img.load()
|
|
except UnidentifiedImageError:
|
|
errors.append("Unidentified image format or corrupted file")
|
|
except IOError as e:
|
|
errors.append(f"IOError: {str(e)}")
|
|
except Exception as e:
|
|
errors.append(f"Unknown error: {str(e)}")
|
|
return errors
|
|
|
|
def fix_error(file_path, error_type):
|
|
"""Fix errors in an image file, if possible."""
|
|
if error_type == "Permission Denied":
|
|
|
|
try:
|
|
os.chmod(file_path, 0o644)
|
|
return "Permissions fixed. File permissions were successfully updated."
|
|
except Exception as e:
|
|
return f"Failed to fix permissions. Ensure you have the necessary permissions to modify this file. Error: {str(e)}"
|
|
elif error_type == "Unidentified image format or corrupted file":
|
|
return "The file format is unrecognized or the file is corrupted. Please try to re-download or restore from a backup."
|
|
elif error_type == "Invalid Format":
|
|
return ("Cannot automatically fix invalid formats. Ensure the file extension matches the file content.\n"
|
|
"To fix this issue, please follow these steps:\n"
|
|
"1. Identify the correct file format by using a file type checker tool (e.g., CheckFileType.com).\n"
|
|
"2. Rename the file with the correct extension:\n"
|
|
" - Right-click on the file and select 'Rename'.\n"
|
|
" - Change the file extension to the correct format (e.g., .jpg, .png).\n"
|
|
" - Press Enter to save the changes.\n"
|
|
"3. Try opening the file again. If the problem persists, the file may be corrupted or contain unsupported data.")
|
|
elif error_type == "File Not Found":
|
|
return ("The file could not be found at the specified path. Please ensure that the file has not been moved or deleted.\n"
|
|
"1. Verify the file path is correct.\n"
|
|
"2. If the file has been moved, update the path in the application or locate the file manually.\n"
|
|
"3. If the file was deleted, check your Recycle Bin or use a data recovery tool to attempt to restore it.")
|
|
elif error_type == "File Path Too Long":
|
|
return ("The file path exceeds the system limit. Windows has a maximum path length of 260 characters.\n"
|
|
"To fix this issue:\n"
|
|
"1. Move the file to a location with a shorter path, closer to the root directory (e.g., C:\\).\n"
|
|
"2. Rename folders in the path to shorter names.\n"
|
|
"3. Enable long path support in Windows if using Windows 10 (version 1607 hoặc cao hơn):\n"
|
|
" - Open the Group Policy Editor (gpedit.msc).\n"
|
|
" - Navigate to 'Local Computer Policy > Computer Configuration > Administrative Templates > System > Filesystem'.\n"
|
|
" - Enable the 'Enable Win32 long paths' option.")
|
|
elif error_type == "Transmission Errors":
|
|
return ("The file may be incomplete or corrupted due to transmission errors. This can happen if the file was downloaded or transferred incorrectly.\n"
|
|
"To fix this issue:\n"
|
|
"1. Re-download hoặc re-transfer the file.\n"
|
|
"2. Use a reliable network connection to ensure the file is not corrupted during transfer.\n"
|
|
"3. Verify the integrity of the file by comparing checksums (if available).")
|
|
elif error_type == "Unsupported Format":
|
|
return ("The file format is not supported by this application. Supported formats include JPEG, PNG, GIF, BMP, TIFF, SVG, và WEBP.\n"
|
|
"To fix this issue:\n"
|
|
"1. Convert the file to a supported format using an image converter tool.\n"
|
|
"2. Ensure that the file is not corrupted and can be opened with other image viewers or editors.")
|
|
else:
|
|
return "No action taken. This error type is not recognized or cannot be fixed automatically."
|
|
|
|
def delete_file(file_path):
|
|
"""Delete the specified file."""
|
|
try:
|
|
os.remove(file_path)
|
|
|
|
selected_files.remove(file_path)
|
|
num_files_var.set(f"{len(selected_files)} files selected.")
|
|
return f"File {file_path} deleted successfully."
|
|
except Exception as e:
|
|
return f"Failed to delete file {file_path}. Error: {str(e)}"
|
|
|
|
def process_image(file_path, q):
|
|
if stop_processing:
|
|
return
|
|
|
|
errors = detect_errors(file_path)
|
|
if errors:
|
|
q.put((file_path, errors))
|
|
|
|
def worker():
|
|
try:
|
|
progress.set(0)
|
|
total_files = len(selected_files)
|
|
thread_count = int(thread_count_var.get())
|
|
for i, input_path in enumerate(selected_files, 1):
|
|
if stop_processing:
|
|
break
|
|
|
|
thread = threading.Thread(target=process_image, args=(input_path, q))
|
|
thread.start()
|
|
thread.join()
|
|
|
|
|
|
progress.set(int(i / total_files * 100))
|
|
|
|
q.put(None)
|
|
except Exception as e:
|
|
if not stop_processing:
|
|
q.put(e)
|
|
|
|
def update_progress():
|
|
try:
|
|
global error_list
|
|
error_list = []
|
|
while True:
|
|
item = q.get()
|
|
if item is None:
|
|
break
|
|
if isinstance(item, tuple):
|
|
error_list.append(item)
|
|
|
|
if not error_list:
|
|
messagebox.showinfo("No Errors Found", "No errors were detected in the selected images.")
|
|
else:
|
|
errors_var.set(f"Errors: {len(error_list)}")
|
|
display_errors(error_list)
|
|
except Exception as e:
|
|
if not stop_processing:
|
|
root.after(0, status_var.set, f"Error: {e}")
|
|
|
|
def update_error_display():
|
|
"""Update the display of errors in the error window."""
|
|
global error_list, frame
|
|
|
|
for widget in frame.winfo_children():
|
|
widget.destroy()
|
|
|
|
|
|
for file_path, errors in error_list:
|
|
for error in errors:
|
|
frame_row = tk.Frame(frame)
|
|
frame_row.pack(fill="x", pady=2)
|
|
|
|
|
|
file_label = tk.Label(frame_row, text=f"{file_path}: {error}", anchor="w", wraplength=500, justify='left')
|
|
file_label.pack(side=tk.LEFT, fill="x", expand=True)
|
|
|
|
delete_button = tk.Button(frame_row, text="Delete", command=lambda fp=file_path: delete_file_action(fp))
|
|
delete_button.pack(side=tk.RIGHT)
|
|
|
|
fix_button = tk.Button(frame_row, text="Fix", command=lambda fp=file_path, et=error: fix_error_action(fp, et))
|
|
fix_button.pack(side=tk.RIGHT)
|
|
|
|
|
|
separator = ttk.Separator(frame, orient='horizontal')
|
|
separator.pack(fill='x', pady=5)
|
|
|
|
frame.update_idletasks()
|
|
canvas.configure(scrollregion=canvas.bbox("all"))
|
|
|
|
def display_errors(error_list):
|
|
global error_window, canvas, frame
|
|
|
|
if 'error_window' in globals() and error_window.winfo_exists():
|
|
error_window.lift()
|
|
else:
|
|
error_window = tk.Toplevel(root)
|
|
error_window.title("Error Details")
|
|
error_window.geometry("600x400")
|
|
|
|
canvas = tk.Canvas(error_window)
|
|
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
|
|
|
scrollbar = tk.Scrollbar(error_window, command=canvas.yview)
|
|
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
|
|
|
canvas.configure(yscrollcommand=scrollbar.set)
|
|
|
|
frame = tk.Frame(canvas)
|
|
canvas.create_window((0, 0), window=frame, anchor="nw")
|
|
|
|
update_error_display()
|
|
|
|
def fix_error_action(file_path, error_type):
|
|
fix_message = fix_error(file_path, error_type)
|
|
messagebox.showinfo("Fix Error", fix_message)
|
|
|
|
def delete_file_action(file_path):
|
|
global error_list
|
|
delete_message = delete_file(file_path)
|
|
messagebox.showinfo("Delete File", delete_message)
|
|
|
|
error_list = [(fp, errs) for fp, errs in error_list if fp != file_path]
|
|
errors_var.set(f"Errors: {len(error_list)}")
|
|
update_error_display()
|
|
|
|
def delete_all_errors():
|
|
global error_list
|
|
for file_path, _ in error_list:
|
|
delete_file(file_path)
|
|
error_list.clear()
|
|
errors_var.set("Errors: 0")
|
|
messagebox.showinfo("Delete All Files", "All files with errors have been deleted.")
|
|
update_error_display()
|
|
|
|
def scan_and_fix():
|
|
global stop_processing, error_messages
|
|
stop_processing = False
|
|
error_messages.clear()
|
|
errors_var.set("Errors: 0")
|
|
if not selected_files:
|
|
status_var.set("Please select images to scan.")
|
|
return
|
|
|
|
threading.Thread(target=worker).start()
|
|
threading.Thread(target=update_progress).start()
|
|
|
|
def stop_processing_func():
|
|
global stop_processing
|
|
stop_processing = True
|
|
status_var.set("Processing stopped.")
|
|
|
|
def return_to_menu():
|
|
stop_processing_func()
|
|
root.destroy()
|
|
import main
|
|
main.open_main_menu()
|
|
|
|
def on_closing():
|
|
return_to_menu()
|
|
|
|
|
|
validate_command = root.register(validate_number)
|
|
|
|
back_button = tk.Button(root, text="<-", font=('Helvetica', 14), command=return_to_menu)
|
|
back_button.pack(anchor='nw', padx=10, pady=10)
|
|
|
|
title_label = tk.Label(root, text="Image Error Fix", font=('Helvetica', 16))
|
|
title_label.pack(pady=10)
|
|
|
|
select_files_button = tk.Button(root, text="Select Files", command=select_files)
|
|
select_files_button.pack(pady=5)
|
|
|
|
num_files_label = tk.Label(root, textvariable=num_files_var)
|
|
num_files_label.pack(pady=5)
|
|
|
|
thread_count_label = tk.Label(root, text="Number of Threads:")
|
|
thread_count_label.pack(pady=5)
|
|
|
|
thread_count_var = tk.StringVar(value="4")
|
|
thread_count_entry = tk.Entry(root, textvariable=thread_count_var, width=3, justify='center', validate="key", validatecommand=(validate_command, '%P'))
|
|
thread_count_entry.pack(pady=5)
|
|
|
|
|
|
separator = ttk.Separator(root, orient='horizontal')
|
|
separator.pack(fill='x', pady=10)
|
|
|
|
scan_fix_button = tk.Button(root, text="Scan and Fix", command=scan_and_fix)
|
|
scan_fix_button.pack(pady=10)
|
|
|
|
stop_button = tk.Button(root, text="Stop", command=stop_processing_func)
|
|
stop_button.pack(pady=5)
|
|
|
|
progress_bar = ttk.Progressbar(root, variable=progress, maximum=100)
|
|
progress_bar.pack(pady=5, fill=tk.X)
|
|
|
|
delete_all_label = tk.Label(root, text="Click 'Delete All' to remove all files with errors.")
|
|
delete_all_label.pack(pady=5)
|
|
|
|
delete_all_button = tk.Button(root, text="Delete All", command=delete_all_errors)
|
|
delete_all_button.pack(pady=5)
|
|
|
|
errors_label = tk.Label(root, textvariable=errors_var, fg="red")
|
|
errors_label.pack(pady=5)
|
|
|
|
status_label = tk.Label(root, textvariable=status_var, fg="green")
|
|
status_label.pack(pady=5)
|
|
|
|
center_window(root)
|
|
root.protocol("WM_DELETE_WINDOW", on_closing)
|
|
root.mainloop()
|
|
|
|
if __name__ == "__main__":
|
|
open_image_error_fix()
|
|
|