需求

  1. 能够选择两个目录,比较这两个目录下所有的图片文件。
  2. 比较图片文件时,记录文件路径、文件名和文件哈希值,并找出具有相同哈希值和不同哈希值的图片。
  3. 将比较结果记录到两个不同的日志文件中,一个包含相同的图片,另一个包含不同的图片。
  4. 能够将不同的文件从一个目录复制到另一个目录。
  5. 复制文件时应保留文件夹结构,并创建目标文件夹(如果不存在)。
  6. 在界面上显示统计结果,包括相同文件数和不同文件数。
  7. 在界面上显示日志文件的路径。

程序代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
import os
import tkinter as tk
from tkinter import filedialog
import hashlib
import shutil


def calculate_hash(file_path):
    """计算文件的MD5哈希值"""
    md5 = hashlib.md5()
    with open(file_path, 'rb') as f:
        while True:
            data = f.read(8192)
            if not data:
                break
            md5.update(data)
    return md5.hexdigest()


def select_directory(entry_widget):
    """使用文件对话框选择目录并将目录路径填入文本框"""
    directory = filedialog.askdirectory()
    if directory:
        entry_widget.delete(0, tk.END)
        entry_widget.insert(0, directory)


def scan_directory(directory):
    """扫描目录下的所有图片文件,并记录路径、文件名和hash"""
    image_files = []
    for root, dirs, files in os.walk(directory):
        for file in files:
            if file.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.bmp')):
                file_path = os.path.join(root, file)
                file_hash = calculate_hash(file_path)
                image_files.append((file_path, file, file_hash))
    return image_files


def compare_directories():
    """比较两个目录的图片文件,并记录结果到日志文件"""
    directory1 = entry1.get()
    directory2 = entry2.get()

    if not directory1 or not directory2:
        result_label.config(text="请提供两个目录路径")
        return

    image_files1 = scan_directory(directory1)
    image_files2 = scan_directory(directory2)

    same_images = []
    different_images = []

    for file1 in image_files1:
        for file2 in image_files2:
            if file1[2] == file2[2]:
                same_images.append((file1[0], file2[0]))
                break
        else:
            different_images.append(file1[0])

    with open('same_images.txt', 'w') as same_file:
        for pair in same_images:
            same_file.write(f"{pair[0]}\t{pair[1]}\n")

    with open('different_images.txt', 'w') as different_file:
        for file_path in different_images:
            different_file.write(f"{file_path}\n")

    same_count = len(same_images)
    different_count = len(different_images)
    log_path = os.path.abspath('different_images.txt')

    # 更新统计结果和日志路径
    stats_label.config(text=f"相同文件数: {same_count}\n不同文件数: {different_count}")
    log_label.config(text=f"日志路径: {log_path}")


def copy_different_to_directory1():
    """复制不同的文件到目录1"""
    directory1 = entry1.get()
    directory2 = entry2.get()

    if not directory1 or not directory2:
        result_label.config(text="请提供两个目录路径")
        return

    copy_different_files(directory2, directory1)
    result_label.config(text="复制不同的文件到目录1完成")


def copy_different_to_directory2():
    """复制不同的文件到目录2"""
    directory1 = entry1.get()
    directory2 = entry2.get()

    if not directory1 or not directory2:
        result_label.config(text="请提供两个目录路径")
        return

    copy_different_files(directory1, directory2)
    result_label.config(text="复制不同的文件到目录2完成")


def copy_different_files(source_dir, dest_dir):
    """复制source_dir中的不同文件到dest_dir中,保留文件夹结构"""
    for root, _, files in os.walk(source_dir):
        for file in files:
            source_file_path = os.path.join(root, file)
            relative_path = os.path.relpath(source_file_path, source_dir)
            dest_file_path = os.path.join(dest_dir, relative_path)

            dest_dir_path = os.path.dirname(dest_file_path)
            os.makedirs(dest_dir_path, exist_ok=True)  # 创建目标文件夹

            if not os.path.exists(dest_file_path):
                shutil.copy2(source_file_path, dest_file_path)


# 创建GUI窗口
root = tk.Tk()
root.title("图片比较和复制工具")

# 创建GUI元素
frame1 = tk.Frame(root)
frame1.pack(fill=tk.BOTH, expand=True)

label1 = tk.Label(frame1, text="目录1:")
label1.grid(row=0, column=0, padx=10, pady=10, sticky="w")
entry1 = tk.Entry(frame1)
entry1.grid(row=0, column=1, padx=10, pady=10, sticky="ew")
entry1.insert(0, "请输入目录1的路径")
browse1_button = tk.Button(frame1, text="选择文件夹", command=lambda: select_directory(entry1))
browse1_button.grid(row=0, column=2, padx=10, pady=10, sticky="e")

# 设置entry1的最小宽度为50个字符
entry1.config(width=50)

frame2 = tk.Frame(root)
frame2.pack(fill=tk.BOTH, expand=True)

label2 = tk.Label(frame2, text="目录2:")
label2.grid(row=0, column=0, padx=10, pady=10, sticky="w")
entry2 = tk.Entry(frame2)
entry2.grid(row=0, column=1, padx=10, pady=10, sticky="ew")
entry2.insert(0, "请输入目录2的路径")
browse2_button = tk.Button(frame2, text="选择文件夹", command=lambda: select_directory(entry2))
browse2_button.grid(row=0, column=2, padx=10, pady=10, sticky="e")

frame_buttons = tk.Frame(root)
frame_buttons.pack(fill=tk.BOTH, expand=True)

compare_button = tk.Button(frame_buttons, text="比较图片", command=compare_directories)
compare_button.grid(row=0, column=0, padx=10, pady=10)

copy_to_dir1_button = tk.Button(frame_buttons, text="复制不同文件到目录1", command=copy_different_to_directory1)
copy_to_dir1_button.grid(row=0, column=1, padx=10, pady=10)

copy_to_dir2_button = tk.Button(frame_buttons, text="复制不同文件到目录2", command=copy_different_to_directory2)
copy_to_dir2_button.grid(row=0, column=2, padx=10, pady=10)

# 创建结果标签
result_label = tk.Label(root, text="")
result_label.pack()

# 创建统计结果和日志路径标签
result_frame = tk.Frame(root)
result_frame.pack(fill=tk.BOTH, expand=True)

stats_label = tk.Label(result_frame, text="", anchor="w")
stats_label.pack(fill=tk.BOTH, expand=True)

log_label = tk.Label(result_frame, text="", anchor="w")
log_label.pack(fill=tk.BOTH, expand=True)

# 设置窗口最小宽度为label1、entry1和browse1_button的宽度之和
min_width = label1.winfo_reqwidth() + entry1.winfo_reqwidth() + browse1_button.winfo_reqwidth() + 60  # 添加一些额外空间
root.update_idletasks()  # 等待窗口更新,确保winfo_reqwidth()能够正确获取值
root.minsize(min_width, 300)  # 设置最小宽度

# 居中显示窗口
root.geometry(
    f"{min_width}x300+{root.winfo_screenwidth() // 2 - min_width // 2}+{root.winfo_screenheight() // 2 - 150}")

# 配置列权重,使entry1可以随窗口变宽
frame1.grid_columnconfigure(1, weight=1)
frame2.grid_columnconfigure(1, weight=1)

# 运行GUI主循环
root.mainloop()