import os
import re
import threading
import time
from tkinter import (
Tk, Frame, Button, Label, Entry, Text, Scrollbar,
filedialog, StringVar, END, DISABLED, NORMAL
)
from PIL import Image
import pytesseract
# 【Windows 必须改成你自己的 tesseract.exe 路径】
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
class ImageRenameTool:
def __init__(self, root):
self.root = root
self.root.title("照片时间提取批量重命名工具")
self.root.geometry("850x650")
# 状态控制
self.folder_path = StringVar()
self.is_running = False
self.is_paused = False
self.task_thread = None
self.file_list = []
self.current_index = 0
self._init_ui()
def _init_ui(self):
# 文件夹选择
frame_path = Frame(self.root)
frame_path.pack(pady=10, fill="x", padx=20)
Label(frame_path, text="图片文件夹:").pack(side="left")
Entry(frame_path, textvariable=self.folder_path, width=65).pack(side="left", padx=5)
Button(frame_path, text="选择文件夹", command=self._select_folder).pack(side="left")
# 控制按钮
frame_ctrl = Frame(self.root)
frame_ctrl.pack(pady=10, fill="x", padx=20)
self.btn_start = Button(frame_ctrl, text="开始处理", command=self._start_task, width=12)
self.btn_start.pack(side="left", padx=5)
self.btn_pause = Button(frame_ctrl, text="暂停", command=self._pause_task, width=12, state=DISABLED)
self.btn_pause.pack(side="left", padx=5)
self.btn_stop = Button(frame_ctrl, text="停止", command=self._stop_task, width=12, state=DISABLED)
self.btn_stop.pack(side="left", padx=5)
# 日志框
frame_log = Frame(self.root)
frame_log.pack(pady=10, fill="both", expand=True, padx=20)
Label(frame_log, text="运行日志:").pack(anchor="w")
self.log_text = Text(frame_log, height=22)
scroll = Scrollbar(frame_log, command=self.log_text.yview)
self.log_text.config(yscrollcommand=scroll.set)
self.log_text.pack(side="left", fill="both", expand=True)
scroll.pack(side="right", fill="y")
def _select_folder(self):
folder = filedialog.askdirectory()
if folder:
self.folder_path.set(folder)
self._log(f"已选择:{folder}")
exts = ('.jpg', '.jpeg', '.png', '.bmp', '.tif')
self.file_list = [os.path.join(folder, f) for f in os.listdir(folder) if f.lower().endswith(exts)]
self._log(f"找到 {len(self.file_list)} 张图片")
def _log(self, msg):
t = time.strftime("%Y-%m-%d %H:%M:%S")
self.log_text.insert(END, f"[{t}] {msg}\n")
self.log_text.see(END)
self.root.update_idletasks()
def _extract_time(self, path):
try:
img = Image.open(path).convert('L')
text = pytesseract.image_to_string(img, lang='eng')
# 匹配日期时间:2025-01-01 12:34:56 / 2025/01/01 12:34:56
match = re.search(r'(\d{4}[-/]\d{1,2}[-/]\d{1,2}[\sT]\d{1,2}:\d{1,2}:\d{1,2})', text)
if match:
raw = match.group(1)
clean = re.sub(r'[^0-9]', '', raw)
if len(clean) >= 14:
return clean[:14]
return clean
return None
except:
return None
def _rename(self, old_path, time_str):
try:
dirname = os.path.dirname(old_path)
ext = os.path.splitext(old_path)[1]
new_name = f"{time_str}{ext}"
new_path = os.path.join(dirname, new_name)
i = 1
while os.path.exists(new_path):
new_name = f"{time_str}_{i}{ext}"
new_path = os.path.join(dirname, new_name)
i += 1
os.rename(old_path, new_path)
return True, new_name
except Exception as e:
return False, str(e)
def _worker(self):
total = len(self.file_list)
self._log(f"开始处理,共 {total} 个文件")
while self.current_index < total and self.is_running:
if self.is_paused:
time.sleep(0.3)
continue
path = self.file_list[self.current_index]
self._log(f"处理:{os.path.basename(path)}")
t = self._extract_time(path)
if t:
ok, name = self._rename(path, t)
if ok:
self._log(f"→ 重命名为:{name}")
else:
self._log(f"→ 重命名失败:{name}")
else:
self._log("→ 未识别到时间,跳过")
self.current_index += 1
time.sleep(0.1)
if self.is_running:
self._log("✅ 全部处理完成")
else:
self._log("⏹️ 任务已停止")
self.is_running = False
self.btn_start.config(state=NORMAL)
self.btn_pause.config(state=DISABLED)
self.btn_stop.config(state=DISABLED)
def _start_task(self):
if not self.folder_path.get():
self._log("⚠️ 请先选择文件夹")
return
self.is_running = True
self.is_paused = False
self.current_index = 0
self.btn_start.config(state=DISABLED)
self.btn_pause.config(state=NORMAL)
self.btn_stop.config(state=NORMAL)
self.task_thread = threading.Thread(target=self._worker, daemon=True)
self.task_thread.start()
def _pause_task(self):
if not self.is_running:
return
self.is_paused = not self.is_paused
self.btn_pause.config(text="恢复" if self.is_paused else "暂停")
self._log("⏸️ 已暂停" if self.is_paused else "▶️ 已恢复")
def _stop_task(self):
self.is_running = False
self.is_paused = False
self.btn_start.config(state=NORMAL)
self.btn_pause.config(state=DISABLED)
self.btn_stop.config(state=DISABLED)
self._log("⏹️ 正在停止...")
if __name__ == "__main__":
root = Tk()
app = ImageRenameTool(root)
root.mainloop()
运行:pip install pillow pytesseract
安装 Tesseract-OCR
把上面代码复制运行
完美使用:窗口界面 + 批量提取时间 + 重命名 + 暂停 + 停止 + 日志
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
import threading
import win32file
import win32api
import os
import time
# JPG 固定特征码
JPG_START = b'\xff\xd8\xff'
JPG_END = b'\xff\xd9'
# 保存路径
SAVE_PATH = r"D:\11"
class JpgRecoveryTool:
def __init__(self, root):
self.root = root
self.root.title("磁盘底层JPG恢复工具 - 管理员运行")
self.root.geometry("750x550")
self.root.resizable(False, False)
# 运行控制变量
self.running = False
self.paused = False
self.stop_flag = False
self.scan_thread = None
# 创建界面
self.create_widgets()
# 自动扫描物理磁盘
self.scan_physical_disks()
def create_widgets(self):
# 顶部:磁盘选择区域
frame_disk = tk.Frame(self.root, pady=10)
frame_disk.pack(fill=tk.X, padx=10)
tk.Label(frame_disk, text="选择物理磁盘:", font=("微软雅黑", 10)).pack(side=tk.LEFT, padx=5)
self.disk_var = tk.StringVar()
self.disk_combo = ttk.Combobox(frame_disk, textvariable=self.disk_var, width=50, state="readonly")
self.disk_combo.pack(side=tk.LEFT, padx=5)
# 中间:按钮控制区
frame_btn = tk.Frame(self.root, pady=5)
frame_btn.pack(fill=tk.X, padx=10)
self.start_btn = tk.Button(frame_btn, text="开始扫描", width=12, command=self.start_scan, bg="#4CAF50", fg="white")
self.start_btn.pack(side=tk.LEFT, padx=5)
self.pause_btn = tk.Button(frame_btn, text="暂停", width=12, command=self.pause_scan, bg="#FFC107", state=tk.DISABLED)
self.pause_btn.pack(side=tk.LEFT, padx=5)
self.stop_btn = tk.Button(frame_btn, text="停止", width=12, command=self.stop_scan, bg="#F44336", fg="white", state=tk.DISABLED)
self.stop_btn.pack(side=tk.LEFT, padx=5)
# 日志显示区域
tk.Label(self.root, text="运行日志:", font=("微软雅黑", 10), anchor="w").pack(fill=tk.X, padx=10, pady=(5, 0))
self.log_text = scrolledtext.ScrolledText(self.root, width=90, height=28, font=("Consolas", 9))
self.log_text.pack(padx=10, pady=5)
# 自动获取本机物理磁盘
def scan_physical_disks(self):
disks = []
for i in range(16): # 扫描0-15号物理磁盘
try:
disk_path = f"\\\\.\\PhysicalDrive{i}"
# 测试是否能打开
handle = win32file.CreateFile(
disk_path, win32file.GENERIC_READ,
win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE,
None, win32file.OPEN_EXISTING, 0, None
)
if handle != win32file.INVALID_HANDLE_VALUE:
disks.append(f"物理磁盘{i} - {disk_path}")
win32file.CloseHandle(handle)
except:
continue
self.disk_combo["values"] = disks
if disks:
self.disk_combo.current(0)
self.log(f"✅ 找到 {len(disks)} 个物理磁盘")
else:
self.log("❌ 未找到物理磁盘,请以管理员身份运行!")
# 日志输出
def log(self, msg):
current_time = time.strftime("%H:%M:%S")
self.log_text.insert(tk.END, f"[{current_time}] {msg}\n")
self.log_text.see(tk.END)
self.root.update()
# 开始扫描
def start_scan(self):
if not self.disk_var.get():
messagebox.showerror("错误", "请先选择磁盘!")
return
# 创建保存目录
if not os.path.exists(SAVE_PATH):
os.makedirs(SAVE_PATH)
# 按钮状态
self.start_btn.config(state=tk.DISABLED)
self.pause_btn.config(state=tk.NORMAL)
self.stop_btn.config(state=tk.NORMAL)
# 重置状态
self.running = True
self.paused = False
self.stop_flag = False
# 启动线程
self.scan_thread = threading.Thread(target=self.do_scan, daemon=True)
self.scan_thread.start()
# 暂停/继续
def pause_scan(self):
if not self.running:
return
if not self.paused:
self.paused = True
self.pause_btn.config(text="继续")
self.log("⏸ 扫描已暂停")
else:
self.paused = False
self.pause_btn.config(text="暂停")
self.log("▶ 扫描继续")
# 停止扫描
def stop_scan(self):
if not self.running:
return
self.stop_flag = True
self.running = False
self.paused = False
self.log("⏹ 手动停止扫描")
self.reset_buttons()
# 重置按钮
def reset_buttons(self):
self.start_btn.config(state=tk.NORMAL)
self.pause_btn.config(state=tk.DISABLED, text="暂停")
self.stop_btn.config(state=tk.DISABLED)
# 核心:磁盘裸扇区读取 + JPG搜索
def do_scan(self):
disk_path = self.disk_var.get().split(" - ")[-1]
self.log(f"