macOS와 iOS의 한글이름 파일을 윈도우로 가져왔을 때, 자음과 모음이 분리된 한글을 경로를 지정하여 일괄 변경하는 파이썬 코드

macOS와 iOS의 한글이름 파일을 윈도우로 가져왔을 때, 자음과 모음이 분리된 한글을 경로를 지정하여 일괄 변경하는 파이썬 코드 | 지식에 대한 탐구

 

v2.1 업데이트 버전 바로가기

 


Python으로 만드는 Mac/iOS-Windows 한글 파일명 정규화 프로그램 – Unicode NFD/NFC 정규화 마스터 클래스

 

소개와 문제 상황

Mac이나 iPhone에서 작업한 한글 파일을 Windows PC로 옮기면서 파일명이 깨져 낯선 형태로 보인 경험이 있으신가요? ‘보고서_최종.txt’가 갑자기 ‘ㅂㅗㄱㅗㅅㅓ_ㅊㅗㅣㅈㅗㅇ.txt’처럼 변하는 현상 말이죠. 이런 현상은 단순한 인코딩 오류가 아닌, 운영체제 간 유니코드 정규화 방식의 차이에서 비롯됩니다.

현재 많은 기업과 개인들이 크로스 플랫폼 환경에서 작업하고 있으며, 이로 인해 발생하는 파일명 호환성 문제는 실무에서 자주 마주치는 골칫거리입니다. 특히 한글을 사용하는 환경에서 이 문제는 더욱 두드러지게 나타납니다.

 

유니코드 정규화의 이해

운영체제마다 다른 유니코드 정규화 방식을 채택하고 있다는 사실, 알고 계셨나요? 이것이 바로 우리가 겪는 문제의 핵심입니다.

운영체제별 유니코드 정규화 방식의 특징은 다음과 같습니다:

운영체제 정규화 방식 특징
MacOS/iOS NFD (Normalization Form Decomposition) 모든 문자를 기본 요소로 분해하여 저장
한글의 경우 자음과 모음을 별도로 저장
Windows NFC (Normalization Form Composition) 문자를 결합된 형태로 저장
한글을 완성된 글자 형태로 유지

정규화 과정을 시각적으로 이해하기 위해 다음 다이어그램을 살펴보겠습니다:

graph TD
    A[맥 환경: '한글.txt'] -->|NFD 변환| B[저장: 'ㅎㅏㄴㄱㅡㄹ.txt']
    B -->|Windows 전송| C[Windows에서 표시: 'ㅎㅏㄴㄱㅡㄹ.txt']
    C -->|NFC 정규화| D[정규화 후: '한글.txt']
    
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#bbf,stroke:#333,stroke-width:2px
    style C fill:#fcc,stroke:#333,stroke-width:2px
    style D fill:#cfc,stroke:#333,stroke-width:2px
      

 

프로그램 구조와 핵심 기능

이제 본격적으로 프로그램의 내부를 들여다보겠습니다. 우리가 만들 프로그램은 직관적인 GUI를 통해 파일명 정규화 작업을 손쉽게 처리할 수 있도록 설계되었습니다.

프로그램의 핵심 구성요소는 다음과 같습니다:

  • tkinter: Python 표준 GUI 프레임워크
  • os: 파일 시스템 작업 처리
  • unicodedata: 유니코드 정규화 처리

 

GUI 클래스 구현

프로그램의 뼈대가 되는 FileNameNormalizer 클래스를 살펴보겠습니다.

# GUI 클래스 정의
class FileNameNormalizer(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("한글 파일명 정규화 프로그램")  # 프로그램 창 제목 설정
        self.geometry("600x400")  # 창 크기 설정 (가로 600px, 세로 400px)

이 코드는 프로그램의 기본 창을 설정합니다. 600×400 픽셀의 적당한 크기로, 사용자가 편하게 작업할 수 있는 공간을 제공합니다.

 

사용자 인터페이스 구성

프로그램의 인터페이스는 사용자 편의성을 고려하여 세 가지 주요 영역으로 구성되어 있습니다:

1. 경로 선택 영역

# 경로 선택을 위한 프레임 구성
path_frame = tk.Frame(main_frame)
path_frame.pack(fill=tk.X, pady=(0, 10))
self.path_var = tk.StringVar()
path_entry = tk.Entry(path_frame, textvariable=self.path_var)  # 경로 입력 필드
path_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))

2. 파일 목록 표시 영역

# 파일 목록을 표시할 리스트박스 설정
self.result_list = tk.Listbox(main_frame)
self.result_list.pack(fill=tk.BOTH, expand=True, pady=(0, 10))  # 리스트박스 배치

3. 실행 버튼 영역

# 변환 실행 버튼 설정
convert_btn = tk.Button(main_frame, text="파일명 정규화", command=self.normalize_filenames)
convert_btn.pack()  # 버튼 배치

프로그램의 작업 흐름을 시각화하면 다음과 같습니다:

sequenceDiagram
    participant User as 사용자
    participant Interface as 프로그램 UI
    participant System as 시스템 처리
    
    User->>Interface: 1. 폴더 선택
    Interface->>System: 2. 디렉토리 스캔
    System->>Interface: 3. 파일 목록 반환
    Interface->>User: 4. 대상 파일 표시
    User->>Interface: 5. 정규화 실행
    Interface->>System: 6. 파일명 변환
    System->>Interface: 7. 결과 반환
    Interface->>User: 8. 완료 알림
    

 

파일 시스템 처리 로직

파일 시스템 처리는 프로그램의 핵심 기능입니다. 디렉토리 내의 모든 파일을 검사하고 정규화가 필요한 파일을 식별하는 과정을 살펴보겠습니다.

# 디렉토리 처리 메소드
def browse_directory(self):
    directory = filedialog.askdirectory()  # 폴더 선택 다이얼로그 표시
    if directory:
        self.path_var.set(directory)  # 선택된 경로 저장
        self.scan_directory(directory)  # 디렉토리 스캔 시작

# 디렉토리 스캔 메소드
def scan_directory(self, directory):
    self.result_list.delete(0, tk.END)  # 기존 목록 초기화
    try:
        for filename in os.listdir(directory):  # 디렉토리 내 파일 순회
            if self.needs_normalization(filename):  # 정규화 필요 여부 확인
                self.result_list.insert(tk.END, filename)  # 대상 파일 목록에 추가
    except Exception as e:
        messagebox.showerror("오류", f"디렉토리 스캔 중 오류 발생: {str(e)}")  # 오류 처리

 







 

 

정규화 프로세스 구현

정규화 프로세스는 프로그램의 핵심이며, 안전하고 신뢰성 있는 처리를 위해 꼼꼼한 구현이 필요합니다. 각 단계별로 자세히 살펴보도록 하겠습니다.

 

정규화 필요성 검사

각 파일에 대해 정규화가 필요한지 검사하는 로직은 다음과 같습니다:

# 파일명 정규화 필요 여부 검사
def needs_normalization(self, filename):
    nfc = unicodedata.normalize('NFC', filename)  # NFC 형식으로 정규화
    return filename != nfc  # 원본과 정규화 결과 비교

이 메소드는 다음과 같은 순서로 동작합니다:

  1. 입력된 파일명을 NFC 형식으로 정규화
  2. 원본 파일명과 정규화된 파일명을 비교
  3. 차이가 있다면 정규화가 필요하다고 판단

 

전체 정규화 프로세스

sequenceDiagram
    participant Files as 파일 시스템
    participant Process as 정규화 프로세스
    participant UI as 사용자 인터페이스
    
    UI->>Files: 디렉토리 스캔
    loop 각 파일 검사
        Files->>Process: 파일명 전달
        Process->>Process: NFD/NFC 검사
        Process-->>UI: 정규화 필요 여부
    end
    UI->>Process: 정규화 시작
    loop 대상 파일들
        Process->>Files: 파일명 변경 요청
        Files-->>UI: 처리 결과
    end
    UI->>UI: 결과 표시

실제 정규화를 수행하는 핵심 코드를 살펴보겠습니다:

# 파일명 정규화 실행
def normalize_filenames(self):
    directory = self.path_var.get()
    if not directory:
        messagebox.showwarning("경고", "디렉토리를 선택해주세요.")
        return
    try:
        count = 0
        for filename in os.listdir(directory):
            if self.needs_normalization(filename):
                old_path = os.path.join(directory, filename)
                new_filename = unicodedata.normalize('NFC', filename)
                new_path = os.path.join(directory, new_filename)
                os.rename(old_path, new_path)
                count += 1
        self.result_list.delete(0, tk.END)
        messagebox.showinfo("완료", f"총 {count}개의 파일명이 정규화되었습니다.")
    except Exception as e:
        messagebox.showerror("오류", f"파일명 변환 중 오류 발생: {str(e)}")  # 오류 발생 시 사용자에게 알림

이 정규화 프로세스는 다음과 같은 단계로 진행됩니다:

  1. 디렉토리 경로 유효성 검사
  2. 디렉토리 내 모든 파일 순회
  3. 각 파일에 대한 정규화 필요성 검사
  4. 필요한 경우 파일명 변경 수행
  5. 처리 결과 보고
 

예외 처리와 안전성 확보

파일명을 수정하는 작업은 매우 신중하게 진행되어야 합니다. 잘못된 처리는 파일 손실이나 시스템 문제로 이어질 수 있기 때문입니다. 이러한 위험을 방지하기 위해 우리 프로그램은 철저한 예외 처리 시스템을 구현하고 있습니다.

 

주요 예외 상황 관리

프로그램이 대비하는 주요 예외 상황과 그 처리 방법을 살펴보겠습니다:

# 주요 예외 처리가 포함된 정규화 함수
def normalize_filenames(self):
    try:
        count = 0
        for filename in os.listdir(directory):
            if self.needs_normalization(filename):
                old_path = os.path.join(directory, filename)
                new_filename = unicodedata.normalize('NFC', filename)
                new_path = os.path.join(directory, new_filename)
                
                # 파일 존재 여부 확인
                if os.path.exists(new_path):
                    messagebox.showerror("오류", f"이미 존재하는 파일명입니다: {new_filename}")
                    continue
                    
                try:
                    os.rename(old_path, new_path)
                    count += 1
                except PermissionError:
                    messagebox.showerror("오류", "파일 접근 권한이 없습니다.")
                except OSError as e:
                    messagebox.showerror("오류", f"파일 시스템 오류: {str(e)}")
    except Exception as e:
        messagebox.showerror("오류", f"처리 중 오류가 발생했습니다: {str(e)}")

 

안전성 보장 메커니즘

프로그램의 안전한 실행을 위해 다음과 같은 단계별 검증 과정을 거칩니다:

flowchart TD
    A[파일명 정규화 시작] --> B{디렉토리 선택됨?}
    B -->|No| C[경고 메시지 표시]
    B -->|Yes| D{파일 접근 가능?}
    D -->|No| E[권한 오류 처리]
    D -->|Yes| F{정규화 필요?}
    F -->|No| G[다음 파일로]
    F -->|Yes| H{파일명 충돌?}
    H -->|Yes| I[충돌 처리]
    H -->|No| J[파일명 변경]
    J --> K{변경 성공?}
    K -->|No| L[오류 처리]
    K -->|Yes| M[카운터 증가]
    
    style A fill:#f9f,stroke:#333
    style B fill:#bbf,stroke:#333
    style H fill:#fcc,stroke:#333
    style J fill:#cfc,stroke:#333

 

사용자 피드백 시스템

프로그램은 사용자에게 작업의 진행 상황과 결과를 명확하게 전달하기 위해 다양한 형태의 피드백을 제공합니다:

# 사용자 피드백 메시지 예시
# 성공 메시지
messagebox.showinfo("완료", f"총 {count}개의 파일명이 정규화되었습니다.")

# 경고 메시지
messagebox.showwarning("경고", "디렉토리를 선택해주세요.")

# 오류 메시지
messagebox.showerror("오류", f"파일명 변환 중 오류 발생: {str(e)}")
 

실제 활용과 최적화

프로그램을 실제 환경에서 효과적으로 활용하기 위한 방법과 성능 최적화 전략을 알아보겠습니다. 특히 대용량 파일 처리나 네트워크 드라이브 상의 파일 처리 시에는 더욱 신중한 접근이 필요합니다.

 

실전 활용 시나리오

활용 상황 처리 방법 주의사항
로컬 파일 처리 – 개인 문서 폴더 정리
– 프로젝트 파일 관리
– 다운로드 폴더 정리
– 처리 전 백업 권장
– 시스템 권한 확인
네트워크 드라이브 – 공유 폴더 파일 관리
– 협업 프로젝트 파일 처리
– 네트워크 상태 확인
– 접근 권한 검증
외장 저장장치 – USB 드라이브 파일 정리
– 외장 하드 데이터 관리
– 안전한 제거 확인
– 파일 시스템 호환성 체크

 

성능 최적화 전략

대용량 파일 처리 시 성능을 개선하기 위한 최적화된 코드를 살펴보겠습니다:

# 최적화된 디렉토리 스캔 함수
def scan_directory(self, directory):
    self.result_list.delete(0, tk.END)
    try:
        # 파일 목록을 미리 생성하여 메모리 효율성 개선
        files = os.listdir(directory)
        normalized_files = [f for f in files if self.needs_normalization(f)]
        
        # 처리할 파일 수를 미리 파악
        total_files = len(normalized_files)
        if total_files == 0:
            messagebox.showinfo("알림", "정규화가 필요한 파일이 없습니다.")
            return
        
        # 정규화 대상 파일 표시
        for filename in normalized_files:
            self.result_list.insert(tk.END, filename)
            
    except Exception as e:
        messagebox.showerror("오류", f"디렉토리 스캔 중 오류 발생: {str(e)}")

 

시스템 리소스 관리

graph TD
    A[시스템 리소스 관리] --> B[메모리 관리]
    A --> C[디스크 I/O]
    A --> D[CPU 사용]
    A --> E[UI 반응성]
    
    B --> F[리스트 버퍼링]
    B --> G[점진적 로딩]
    
    C --> H[일괄 처리]
    C --> I[캐시 활용]
    
    D --> J[작업 분배]
    D --> K[우선순위 조정]
    
    E --> L[비동기 처리]
    E --> M[상태 업데이트]
    
    style A fill:#f96,stroke:#333
    style B,C,D,E fill:#9cf,stroke:#333

 

프로그램 발전 방향

현재 구현된 프로그램은 기본적인 파일명 정규화 기능을 충실히 수행하고 있지만, 더욱 강력하고 유용한 도구로 발전시킬 수 있는 여지가 있습니다. 실제 업무 환경에서의 요구사항과 사용자 피드백을 반영하여, 다음과 같은 개선 방향을 제시하고자 합니다.

 

기능 확장 방안

# 향후 확장된 기능을 포함한 클래스 예시
class EnhancedFileNameNormalizer(FileNameNormalizer):
    def __init__(self):
        super().__init__()
        self.history = []  # 변환 이력 저장
        self.backup_path = None  # 백업 경로 저장
        
    def create_backup(self, filepath):
        # 파일 백업 로직 구현
        pass
        
    def restore_from_backup(self, filepath):
        # 백업 복원 로직 구현
        pass
        
    def log_operation(self, operation_details):
        # 작업 로깅 시스템 구현
        pass

 

성능 개선 로드맵

timeline
    section 1단계
        기본 기능 강화 : 백업 시스템
        : 로깅 시스템
        : 에러 처리 개선
    section 2단계
        성능 최적화 : 병렬 처리
        : 메모리 최적화
        : 배치 프로세싱
    section 3단계
        사용자 경험 : UI 개선
        : 진행률 표시
        : 작업 예약
    section 4단계
        확장 기능 : API 통합
        : 플러그인 시스템
        : 자동화 도구

 

통합 및 확장성

확장 영역 구현 방안 기대 효과
자동화 시스템 연동 – 배치 작업 지원
– 스케줄링 기능
– 대량 작업 자동화
– 운영 효율성 증가
클라우드 스토리지 통합 – API 연동
– 동기화 기능
– 원격 작업 지원
– 협업 효율성 향상
버전 관리 시스템 – 변경 이력 관리
– 롤백 기능
– 안전성 강화
– 변경 추적 용이

 

최종 제언

프로그램 개발에 있어 가장 중요한 것은 사용자의 실제 필요를 정확히 파악하고 이를 안정적으로 충족시키는 것입니다. 이 프로그램은 Mac/iOS와 Windows 환경 사이의 한글 파일명 호환성 문제를 효과적으로 해결하면서도, 향후 더 다양한 요구사항을 수용할 수 있는 확장성을 갖추고 있습니다.

 

전체 코드

import tkinter as tk
from tkinter import filedialog, messagebox
import os
import unicodedata

class FileNameNormalizer(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("한글 파일명 정규화 프로그램")
        self.geometry("600x400")

        # 메인 프레임
        main_frame = tk.Frame(self, padx=20, pady=20)
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 경로 입력 프레임
        path_frame = tk.Frame(main_frame)
        path_frame.pack(fill=tk.X, pady=(0, 10))

        self.path_var = tk.StringVar()
        path_entry = tk.Entry(path_frame, textvariable=self.path_var)
        path_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))

        browse_btn = tk.Button(path_frame, text="폴더 선택", command=self.browse_directory)
        browse_btn.pack(side=tk.RIGHT)

        # 결과 리스트박스
        self.result_list = tk.Listbox(main_frame)
        self.result_list.pack(fill=tk.BOTH, expand=True, pady=(0, 10))

        # 변환 버튼
        convert_btn = tk.Button(main_frame, text="파일명 정규화", command=self.normalize_filenames)
        convert_btn.pack()

    def browse_directory(self):
        directory = filedialog.askdirectory()
        if directory:
            self.path_var.set(directory)
            self.scan_directory(directory)

    def scan_directory(self, directory):
        self.result_list.delete(0, tk.END)
        try:
            for filename in os.listdir(directory):
                if self.needs_normalization(filename):
                    self.result_list.insert(tk.END, filename)
        except Exception as e:
            messagebox.showerror("오류", f"디렉토리 스캔 중 오류 발생: {str(e)}")

    def needs_normalization(self, filename):
        nfc = unicodedata.normalize('NFC', filename)
        return filename != nfc

    def normalize_filenames(self):
        directory = self.path_var.get()
        if not directory:
            messagebox.showwarning("경고", "디렉토리를 선택해주세요.")
            return
        try:
            count = 0
            for filename in os.listdir(directory):
                if self.needs_normalization(filename):
                    old_path = os.path.join(directory, filename)
                    new_filename = unicodedata.normalize('NFC', filename)
                    new_path = os.path.join(directory, new_filename)
                    
                    os.rename(old_path, new_path)
                    count += 1
            self.result_list.delete(0, tk.END)
            messagebox.showinfo("완료", f"총 {count}개의 파일명이 정규화되었습니다.")
            
        except Exception as e:
            messagebox.showerror("오류", f"파일명 변환 중 오류 발생: {str(e)}")

if __name__ == "__main__":
    app = FileNameNormalizer()
    app.mainloop()