사진 선별 GUI 앱 초기 커밋
RAW/JPG 매칭 스크립트에 tkinter GUI(폴더 선택)를 추가하고 py2app으로 .app/.dmg 패키징 설정 포함. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
.DS_Store
|
||||||
|
dist/
|
||||||
|
dmg_staging/
|
||||||
|
build/
|
||||||
|
고른 jpg/
|
||||||
|
선별 완료/
|
||||||
+53
@@ -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)
|
||||||
@@ -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()
|
||||||
@@ -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'],
|
||||||
|
)
|
||||||
BIN
Binary file not shown.
Reference in New Issue
Block a user