mac的HEIT图片格式转JPEG格式批量处理器(源代码)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import tkinter as tk
from tkinter import filedialog
from PIL import Image
import pillow_heif
import threading
import concurrent.futures
from queue import Queue
import datetime

# 注册 pillow-heif 插件,让 Pillow 能够打开 HEIF 文件
pillow_heif.register_heif_opener()

# 全局消息队列,用于日志显示
message_queue = Queue()
# 全局错误列表,记录转换失败的 (input_path, output_path) 文件对
error_files = []
# 全局控件引用,便于后续统一设置(在 UI 创建时赋值)
retry_button = None
src_button = None
dest_button = None
start_button = None
format_menu = None
target_format_var = None

def safe_print(message):
    """
    将信息放入队列,由主线程更新日志显示
    """
    message_queue.put(message)

def get_extension_for_format(target_format):
    """
    根据目标格式返回适当的文件扩展名
    """
    mapping = {
        "JPEG": ".jpg",
        "PNG": ".png",
        "BMP": ".bmp",
        "TIFF": ".tiff"
    }
    return mapping.get(target_format.upper(), ".jpg")

def convert_heif_to_format(input_path, output_path, target_format):
    """
    将单个 HEIF/HEIC 文件转换为指定格式文件。
    """
    global error_files
    try:
        with Image.open(input_path) as im:
            if im.mode != "RGB":
                im = im.convert("RGB")
            im.save(output_path, target_format)
            safe_print(f"转换成功: {os.path.basename(input_path)} -> {os.path.basename(output_path)}")
    except Exception as e:
        safe_print(f"转换失败: {os.path.basename(input_path)},错误: {e}")
        error_files.append((input_path, output_path))

def process_files(src_folder, dest_folder, target_format):
    """
    获取源文件夹中所有 HEIF/HEIC 文件,并使用多线程并发转换。
    """
    global error_files
    error_files.clear()  # 清空之前的错误记录
    heif_files = [f for f in os.listdir(src_folder) if f.lower().endswith((".heif", ".heic"))]
    if not heif_files:
        safe_print("所选文件夹中没有找到 HEIF/HEIC 文件!")
        return

    safe_print(f"共发现 {len(heif_files)} 个文件,开始转换...")
    max_workers = min(8, len(heif_files))
    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = []
        for file in heif_files:
            input_path = os.path.join(src_folder, file)
            output_filename = os.path.splitext(file)[0] + get_extension_for_format(target_format)
            output_path = os.path.join(dest_folder, output_filename)
            futures.append(executor.submit(convert_heif_to_format, input_path, output_path, target_format))
        concurrent.futures.wait(futures)
    safe_print("初次转换完成。")

def retry_failed_conversion(target_format):
    """
    对全局 error_files 中记录的失败文件进行自动重试转换。
    """
    global error_files, retry_button
    if not error_files:
        safe_print("无失败文件,无需重试。")
        return
    to_retry = error_files.copy()
    error_files.clear()
    safe_print("开始自动重试转换失败的文件...")
    max_workers = min(8, len(to_retry))
    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = []
        for input_path, output_path in to_retry:
            futures.append(executor.submit(convert_heif_to_format, input_path, output_path, target_format))
        concurrent.futures.wait(futures)
    if error_files:
        safe_print("自动重试后仍有以下文件转换失败:")
        for inp, _ in error_files:
            safe_print(f"  {os.path.basename(inp)}")
        safe_print("请点击‘重试转换失败文件’按钮进行手动重试。")
        if retry_button:
            retry_button.after(0, lambda: retry_button.config(state=tk.NORMAL))
    else:
        safe_print("自动重试后,所有文件转换成功!")

def start_conversion(src, dest, target_format):
    """
    后台线程工作函数:执行初次转换及自动重试。
    """
    safe_print("转换开始...")
    process_files(src, dest, target_format)
    if error_files:
        safe_print("初次转换存在失败文件,启动自动重试...")
        retry_failed_conversion(target_format)
    if error_files:
        safe_print("重试后仍有部分文件转换失败。")
        safe_print("失败文件列表:")
        for inp, _ in error_files:
            safe_print(f"  {os.path.basename(inp)}")
        safe_print("请点击‘重试转换失败文件’按钮进行手动重试。")
    else:
        safe_print("所有文件均转换成功!")

def manual_retry_conversion():
    """
    手动重试转换失败的文件(在后台线程中执行)。
    """
    global retry_button
    retry_button.config(state=tk.DISABLED)
    current_format = target_format_var.get()
    safe_print("手动重试开始...")
    retry_failed_conversion(current_format)
    if error_files:
        safe_print("手动重试后仍有部分文件转换失败,请检查错误。")
    else:
        safe_print("手动重试后,所有失败文件已转换成功!")

def update_log(text_widget):
    """
    定时检查消息队列,将新消息添加到日志文本框中。
    """
    while not message_queue.empty():
        msg = message_queue.get()
        text_widget.insert(tk.END, msg + "\n")
        text_widget.see(tk.END)
    text_widget.after(100, update_log, text_widget)

def browse_folder(entry_widget, title):
    """
    弹出文件夹选择对话框,并更新对应的文本输入框。
    """
    folder = filedialog.askdirectory(title=title, initialdir=entry_widget.get())
    if folder:
        entry_widget.delete(0, tk.END)
        entry_widget.insert(0, folder)

def create_gui():
    """
    创建图形用户界面,包含文件夹选择、目标格式选择、日志展示及转换和重试按钮。
    同时实现自动销毁功能:在 2025-12-31 之后,所有按钮失效。
    """
    global target_format_var, retry_button, format_menu, src_button, dest_button, start_button

    root = tk.Tk()
    root.title("专供EMILY===HEIF 转 文件格式 批量转换工具")
    root.geometry("600x500")

    # --- 源文件夹选择 ---
    src_frame = tk.Frame(root)
    src_frame.pack(fill=tk.X, padx=10, pady=5)
    tk.Label(src_frame, text="选择要转换的图片文件夹:").pack(side=tk.LEFT)
    src_entry = tk.Entry(src_frame, width=50)
    src_entry.pack(side=tk.LEFT, padx=5)
    src_entry.insert(0, os.getcwd())
    src_button = tk.Button(src_frame, text="浏览", command=lambda: browse_folder(src_entry, "选择含有 HEIF 图片的文件夹"))
    src_button.pack(side=tk.LEFT)

    # --- 目标文件夹选择 ---
    dest_frame = tk.Frame(root)
    dest_frame.pack(fill=tk.X, padx=10, pady=5)
    tk.Label(dest_frame, text="选择转换后的图片文件夹:").pack(side=tk.LEFT)
    dest_entry = tk.Entry(dest_frame, width=50)
    dest_entry.pack(side=tk.LEFT, padx=5)
    dest_entry.insert(0, os.getcwd())
    dest_button = tk.Button(dest_frame, text="浏览", command=lambda: browse_folder(dest_entry, "选择保存转换后图片的文件夹"))
    dest_button.pack(side=tk.LEFT)

    # --- 目标格式选择 ---
    format_frame = tk.Frame(root)
    format_frame.pack(fill=tk.X, padx=10, pady=5)
    tk.Label(format_frame, text="目标格式:").pack(side=tk.LEFT)
    target_format_var = tk.StringVar(root)
    format_options = ["JPEG", "PNG", "BMP", "TIFF"]
    target_format_var.set("JPEG")
    format_menu = tk.OptionMenu(format_frame, target_format_var, *format_options)
    format_menu.pack(side=tk.LEFT, padx=5)

    # --- 日志信息展示框 ---
    log_text = tk.Text(root, wrap=tk.WORD, height=15)
    log_text.pack(fill=tk.BOTH, padx=10, pady=10, expand=True)

    # --- 开始转换按钮 ---
    start_button = tk.Button(root, text="开始转换", font=("Arial", 12), width=20, height=2,
                             command=lambda: threading.Thread(
                                 target=start_conversion,
                                 args=(src_entry.get(), dest_entry.get(), target_format_var.get()),
                                 daemon=True
                             ).start())
    start_button.pack(pady=5)

    # --- 手动重试按钮 ---
    retry_button = tk.Button(root, text="重试转换失败文件", font=("Arial", 12), width=25, height=2,
                             command=lambda: threading.Thread(target=manual_retry_conversion, daemon=True).start())
    retry_button.pack(pady=5)
    retry_button.config(state=tk.DISABLED)

    update_log(log_text)

    # --- 自动销毁功能:检查当前日期是否超过 2025-12-31 ---
    if datetime.date.today() > datetime.date(2025, 12, 31):
        safe_print("本软件已到期,所有功能已失效。")
        src_button.config(state=tk.DISABLED)
        dest_button.config(state=tk.DISABLED)
        start_button.config(state=tk.DISABLED)
        retry_button.config(state=tk.DISABLED)
        format_menu.config(state=tk.DISABLED)

    root.mainloop()

if __name__ == "__main__":
    create_gui()

 

posted @ 2025-02-11 14:04  *感悟人生*  阅读(68)  评论(0)    收藏  举报