commit 610cb40ce3482dc3da19dde3c1d8ee2d4d52a0f2 Author: sihoo Date: Tue Aug 11 14:11:30 2026 +0900 사진 선별 GUI 앱 초기 커밋 RAW/JPG 매칭 스크립트에 tkinter GUI(폴더 선택)를 추가하고 py2app으로 .app/.dmg 패키징 설정 포함. Co-Authored-By: Claude Sonnet 5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0927f29 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +dist/ +dmg_staging/ +build/ +고른 jpg/ +선별 완료/ diff --git a/photo_fine b/photo_fine new file mode 100644 index 0000000..aa2f416 --- /dev/null +++ b/photo_fine @@ -0,0 +1,53 @@ +import os +import shutil + +# ========================================================================= +# 알려주신 맥(Mac) 경로를 그대로 입력해 두었습니다. +jpg_dir = r"/Users/hootongerun/Desktop/사진 선별/고른 jpg" +raw_dir = r"/Volumes/K-3/DCIM" +target_dir = r"/Users/hootongerun/Desktop/사진 선별/선별 완료" +# ========================================================================= + +# 결과를 저장할 새 폴더 생성 +if not os.path.exists(target_dir): + os.makedirs(target_dir) + +# 1. 고른 JPG 폴더에서 파일 이름 추출 +try: + jpg_files = os.listdir(jpg_dir) +except FileNotFoundError: + print(f"❌ 오류: JPG 폴더 경로를 찾을 수 없습니다: {jpg_dir}") + print("폴더 이름에 오타가 없거나 실제로 존재하는지 확인해주세요.") + exit() + +jpg_names = {os.path.splitext(f)[0] for f in jpg_files if f.lower().endswith(('.jpg', '.jpeg'))} +print(f"🔍 JPG 폴더에서 총 {len(jpg_names)}개의 사진 이름을 확인했습니다.") + +# 2. RAW 폴더에 있는 파일 목록 가져오기 +try: + raw_files = os.listdir(raw_dir) +except FileNotFoundError: + print(f"❌ 오류: RAW(메모리카드) 폴더 경로를 찾을 수 없습니다: {raw_dir}") + print("메모리카드가 맥에 잘 연결되어 있는지 확인해주세요.") + exit() + +# 펜탁스 PEF 및 주요 RAW 확장자 지정 +raw_extensions = ('.pef', '.arw', '.cr2', '.cr3', '.nef', '.dng', '.orf', '.rw2', '.raf') + +# 3. 이름이 일치하는 PEF 파일 복사 +count = 0 +for f in raw_files: + name, ext = os.path.splitext(f) + + # 확장자가 pef(대소문자 무관)이고 이름이 JPG 목록에 있다면 복사 + if ext.lower() in raw_extensions and name in jpg_names: + src_path = os.path.join(raw_dir, f) + dst_path = os.path.join(target_dir, f) + + shutil.copy(src_path, dst_path) + count += 1 + print(f"[{count}] 매칭 성공 복사 중: {f}") + +print("-" * 50) +print(f"🎉 작업 완료! 선택하신 JPG와 번호가 일치하는 PEF 파일 총 {count}개를 찾아서 '무제 폴더'로 모았습니다.") +print("-" * 50) \ No newline at end of file diff --git a/photo_selector_gui.py b/photo_selector_gui.py new file mode 100644 index 0000000..31092c6 --- /dev/null +++ b/photo_selector_gui.py @@ -0,0 +1,146 @@ +import os +import shutil +import json +import threading +import tkinter as tk +from tkinter import filedialog, scrolledtext + +CONFIG_PATH = os.path.expanduser("~/Library/Application Support/PhotoSelector/config.json") + +RAW_EXTENSIONS = ('.pef', '.arw', '.cr2', '.cr3', '.nef', '.dng', '.orf', '.rw2', '.raf') + + +def load_config(): + try: + with open(CONFIG_PATH, "r", encoding="utf-8") as f: + return json.load(f) + except Exception: + return {} + + +def save_config(data): + os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True) + with open(CONFIG_PATH, "w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=2) + + +class PhotoSelectorApp: + def __init__(self, root): + self.root = root + root.title("사진 선별기") + root.geometry("640x480") + root.resizable(True, True) + + config = load_config() + self.jpg_dir = tk.StringVar(value=config.get("jpg_dir", "")) + self.raw_dir = tk.StringVar(value=config.get("raw_dir", "")) + self.target_dir = tk.StringVar(value=config.get("target_dir", "")) + + self._build_row("1. 고른 JPG 폴더", self.jpg_dir, 0) + self._build_row("2. RAW(메모리카드) 폴더", self.raw_dir, 1) + self._build_row("3. 결과를 저장할 폴더", self.target_dir, 2) + + self.run_button = tk.Button(root, text="실행", command=self.run_clicked, height=2) + self.run_button.grid(row=3, column=0, columnspan=3, sticky="ew", padx=10, pady=10) + + self.log = scrolledtext.ScrolledText(root, state="disabled", wrap="word") + self.log.grid(row=4, column=0, columnspan=3, sticky="nsew", padx=10, pady=(0, 10)) + + root.grid_columnconfigure(1, weight=1) + root.grid_rowconfigure(4, weight=1) + + def _build_row(self, label_text, var, row): + tk.Label(self.root, text=label_text, anchor="w", width=20).grid( + row=row, column=0, sticky="w", padx=10, pady=5 + ) + entry = tk.Entry(self.root, textvariable=var) + entry.grid(row=row, column=1, sticky="ew", padx=5, pady=5) + tk.Button(self.root, text="폴더 선택", command=lambda: self._choose_dir(var)).grid( + row=row, column=2, sticky="e", padx=10, pady=5 + ) + + def _choose_dir(self, var): + initial = var.get() or os.path.expanduser("~") + chosen = filedialog.askdirectory(initialdir=initial) + if chosen: + var.set(chosen) + + def log_write(self, text): + self.log.configure(state="normal") + self.log.insert("end", text + "\n") + self.log.see("end") + self.log.configure(state="disabled") + + def run_clicked(self): + jpg_dir = self.jpg_dir.get().strip() + raw_dir = self.raw_dir.get().strip() + target_dir = self.target_dir.get().strip() + + if not jpg_dir or not raw_dir or not target_dir: + self.log_write("❌ 세 개의 폴더를 모두 선택해주세요.") + return + + save_config({"jpg_dir": jpg_dir, "raw_dir": raw_dir, "target_dir": target_dir}) + + self.run_button.configure(state="disabled") + self.log.configure(state="normal") + self.log.delete("1.0", "end") + self.log.configure(state="disabled") + + thread = threading.Thread( + target=self._run_task, args=(jpg_dir, raw_dir, target_dir), daemon=True + ) + thread.start() + + def _run_task(self, jpg_dir, raw_dir, target_dir): + try: + self._process(jpg_dir, raw_dir, target_dir) + finally: + self.root.after(0, lambda: self.run_button.configure(state="normal")) + + def _process(self, jpg_dir, raw_dir, target_dir): + log = lambda text: self.root.after(0, self.log_write, text) + + if not os.path.exists(target_dir): + os.makedirs(target_dir) + + try: + jpg_files = os.listdir(jpg_dir) + except FileNotFoundError: + log(f"❌ 오류: JPG 폴더 경로를 찾을 수 없습니다: {jpg_dir}") + return + + jpg_names = { + os.path.splitext(f)[0] for f in jpg_files if f.lower().endswith((".jpg", ".jpeg")) + } + log(f"🔍 JPG 폴더에서 총 {len(jpg_names)}개의 사진 이름을 확인했습니다.") + + try: + raw_files = os.listdir(raw_dir) + except FileNotFoundError: + log(f"❌ 오류: RAW(메모리카드) 폴더 경로를 찾을 수 없습니다: {raw_dir}") + return + + count = 0 + for f in raw_files: + name, ext = os.path.splitext(f) + if ext.lower() in RAW_EXTENSIONS and name in jpg_names: + src_path = os.path.join(raw_dir, f) + dst_path = os.path.join(target_dir, f) + shutil.copy(src_path, dst_path) + count += 1 + log(f"[{count}] 매칭 성공 복사 중: {f}") + + log("-" * 50) + log(f"🎉 작업 완료! 총 {count}개의 RAW 파일을 '{target_dir}' 폴더로 모았습니다.") + log("-" * 50) + + +def main(): + root = tk.Tk() + PhotoSelectorApp(root) + root.mainloop() + + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..93a4c72 --- /dev/null +++ b/setup.py @@ -0,0 +1,23 @@ +from setuptools import setup + +APP = ['photo_selector_gui.py'] +OPTIONS = { + 'argv_emulation': False, + 'plist': { + 'CFBundleName': '사진 선별기', + 'CFBundleDisplayName': '사진 선별기', + 'CFBundleIdentifier': 'com.hootongerun.photoselector', + 'CFBundleShortVersionString': '1.0.0', + 'CFBundleVersion': '1.0.0', + 'NSHumanReadableCopyright': '', + 'LSMinimumSystemVersion': '10.13', + 'NSHighResolutionCapable': True, + }, +} + +setup( + app=APP, + name='PhotoSelector', + options={'py2app': OPTIONS}, + setup_requires=['py2app'], +) diff --git a/사진 선별기.dmg b/사진 선별기.dmg new file mode 100644 index 0000000..f31155a Binary files /dev/null and b/사진 선별기.dmg differ