|
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="1") |
|
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() |
|
|