photo_manager_eng_v4.ahk
全構文 ⇒
import os
import sys
import shutil
import socket
import tkinter as tk
from tkinter import messagebox
from PIL import Image, ImageTk, ImageOps
try:
from pillow_heif import register_heif_opener
register_heif_opener()
except ImportError:
pass
lock_socket = None
def is_already_running():
global lock_socket
lock_socket = socket.socket(
socket.AF_INET, socket.SOCK_STREAM)
try:
lock_socket.bind(("127.0.0.1", 65432))
except socket.error:
return True
return False
class PhotoSelectorApp:
def __init__(self, root):
self.root = root
self.root.title("PhotoSelector")
self.root.geometry("1400x850")
# 引数(フォルダパス)が引き渡されていればそれを使い、無ければ自身の場所を使う
if len(sys.argv) > 1 and os.path.isdir(sys.argv[1]):
self.base_dir = os.path.abspath(sys.argv[1])
elif getattr(sys, 'frozen', False):
self.base_dir = os.path.dirname(sys.executable)
else:
self.base_dir = os.path.dirname(os.path.abspath(__file__))
# 必要なフォルダは「selected」のみ生成
self.selected_dir = os.path.join(self.base_dir, "selected")
os.makedirs(self.selected_dir, exist_ok=True)
self.image_list, self.selected_indices, self.saved_indices = [], set(), set()
self.photo_images, self.buttons = [], []
self.button_width, self.current_preview_idx = 180, None
self.last_clicked_idx = None
self.setup_ui()
self.load_images()
def setup_ui(self):
self.paned = tk.PanedWindow(self.root, orient="horizontal", sashwidth=6, bg="
#cccccc", sashrelief="raised")
self.paned.pack(expand=True, fill="both", padx=10, pady=5)
self.left_container = tk.Frame(self.paned, bg="white")
self.paned.add(self.left_container, width=800)
self.canvas = tk.Canvas(self.left_container, bg="white", highlightthickness=0)
self.scrollbar = tk.Scrollbar(self.left_container, orient="vertical", command=self.canvas.yview)
self.scroll_frame = tk.Frame(self.canvas, bg="white")
self.canvas.create_window((0, 0), window=self.scroll_frame, anchor="nw")
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.canvas.pack(side="left", expand=True, fill="both")
self.scrollbar.pack(side="right", fill="y")
self.preview_frame = tk.Frame(self.paned, bg="
#e0e0e0")
self.paned.add(self.preview_frame, width=500)
self.preview_label = tk.Label(self.preview_frame, text="Preview Window", bg="
#e0e0e0", font=("Arial", 12))
self.preview_label.pack(expand=True, fill="both")
bottom_frame = tk.Frame(self.root)
bottom_frame.pack(fill="x", pady=10)
self.stat_label = tk.Label(bottom_frame, text="Total: 0 / Selected: 0", font=("Arial", 18, "bold"), fg="red")
self.stat_label.pack(side="left", padx=20)
btn_config = {"font": ("Arial", 11, "bold"), "padx": 10, "bg": "cyan"}
tk.Button(bottom_frame, text="ALL SELECT", command=
self.select_all, **btn_config).pack(side="left", padx=2)
self.btn_save = tk.Button(bottom_frame, text="SAVE", command=
self.save_selection, **btn_config)
self.btn_save.pack(side="left", padx=2)
tk.Button(bottom_frame, text="CLEAR ALL", command=self.clear_all, **btn_config).pack(side="left", padx=2)
# 旧 PDF PRINT から「selected」ボタンへ変更
self.btn_selected_copy = tk.Button(bottom_frame, text="selected", command=self.copy_to_selected, **btn_config)
self.btn_selected_copy.pack(side="left", padx=2)
self.canvas.bind_all("<MouseWheel>", lambda e: self.canvas.yview_scroll(int(-1*(
e.delta/120)), "units"))
self.scroll_frame.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))
self.canvas.bind("<Configure>", lambda e: self.rearrange_images())
self.preview_frame.bind("<Configure>", lambda e:
self.show_preview(self.current_preview_idx) if self.current_preview_idx is not None else None)
def rearrange_images(self):
w = self.canvas.winfo_width()
cols = max(1, w // (self.button_width 10))
for i, btn in enumerate(self.buttons):
btn.grid_forget()
btn.grid(row=i//cols, column=i%cols, padx=5, pady=5)
def load_images(self):
for btn in self.buttons: btn.destroy()
self.buttons,
self.photo_images, self.image_list = [], [], []
self.selected_indices, self.saved_indices, self.current_preview_idx = set(), set(), None
self.last_clicked_idx = None
# png を対象に追加
exts = (".jpg", ".jpeg", ".heic", ".png")
files = sorted([f for f in os.listdir(self.base_dir) if f.lower().endswith(exts)])
self.image_list = [(f, os.path.join(self.base_dir, f)) for f in files]
for i, (fname, fpath) in enumerate(self.image_list):
try:
img = ImageOps.exif_transpose(
Image.open(fpath))
img.thumbnail((150, 150))
photo = ImageTk.PhotoImage(img)
self.photo_images.append(photo)
btn = tk.Button(self.scroll_frame, image=photo, text=os.path.splitext(fname)[0], compound="top",
width=self.button_width, height=200, wraplength=150, bg="white")
btn.bind("<Button-1>", lambda e, idx=i: self.on_left_click(e, idx))
btn.bind("<Button-3>", lambda e, idx=i: self.on_right_click(idx))
self.buttons.append(btn)
except: continue
self.rearrange_images()
self.update_stats()
def select_all(self):
for i in range(len(self.image_list)):
if i not in self.saved_indices:
self.selected_indices.add(i)
self.buttons[i].config(bg="
#add8e6")
self.update_stats()
def on_left_click(self, event, idx):
if (event.state & 0x0001) and self.last_clicked_idx is not None:
start = min(self.last_clicked_idx, idx)
end = max(self.last_clicked_idx, idx)
for i in range(start, end 1):
if i not in self.saved_indices:
self.selected_indices.add(i)
self.buttons[i].config(bg="
#add8e6")
else:
if idx not in self.saved_indices:
self.selected_indices.add(idx)
self.buttons[idx].config(bg="
#add8e6")
self.last_clicked_idx = idx
self.current_preview_idx = idx
self.show_preview(idx)
self.update_stats()
def on_right_click(self, idx):
if idx in self.selected_indices: self.selected_indices.remove(idx)
if idx in self.saved_indices: self.saved_indices.remove(idx)
self.buttons[idx].config(bg="white")
self.update_stats()
def show_preview(self, idx):
if idx is None: return
try:
img = ImageOps.exif_transpose(
Image.open(self.image_list[idx][1]))
pw, ph = self.preview_frame.winfo_width(), self.preview_frame.winfo_height()
if pw > 20 and ph > 20:
img.thumbnail((pw - 20, ph - 20))
photo = ImageTk.PhotoImage(img)
self.preview_label.config(image=photo, text="")
self.preview_label.image = photo
except: pass
def save_selection(self):
for idx in self.selected_indices:
self.saved_indices.add(idx)
self.buttons[idx].config(bg="
#90ee90")
self.selected_indices.clear()
self.update_stats()
def clear_all(self):
self.load_images()
def update_stats(self):
total, sel = len(self.image_list), len(self.selected_indices) len(self.saved_indices)
self.stat_label.config(text=f"Total: {total} / Selected: {sel}")
def copy_to_selected(self):
"""保存された画像(緑色)を selected フォルダにコピーする"""
targets = sorted(list(self.saved_indices))
if not targets:
messagebox.showwarning("Warning", "No photos saved (Green). Please click SAVE first.")
return
copied_count = 0
for idx in targets:
src_path = self.image_list[idx][1]
dst_path = os.path.join(self.selected_dir, self.image_list[idx][0])
try:
shutil.copy2(src_path, dst_path)
copied_count = 1
except Exception as e:
print(f"Copy error ({self.image_list[idx][0]}): {e}")
messagebox.showinfo("Done", f"Copied {copied_count} files to /selected.")
self.load_images()
if __name__ == "__main__":
if is_already_running(): sys.exit()
root =
tk.Tk()
app = PhotoSelectorApp(root)
root.mainloop()