수백 개 이미지 파일 이름, 파이썬 스크립트로 한 번에 바꿔본 후기

사진 수백 장 파일 이름을 하나씩 바꾸다 손가락이 아파진 적 있으신가요? 파이썬의 os, pathlib, glob 모듈을 조합하면 클릭 한 번으로 수천 개 파일도 원하는 규칙대로 일괄 변경할 수 있거든요.

저도 처음엔 “설마 이게 그렇게 복잡하겠어?” 싶었어요. 블로그에 올릴 여행 사진 400장 정도를 날짜순으로 정리하려고 했는데, 카메라 두 대에서 뽑은 파일이라 이름 규칙이 전부 달랐거든요. DSC_0001부터 IMG_20240315까지 뒤죽박죽. 폴더 열 때마다 한숨부터 나왔어요.

윈도우 탐색기에서 다중 선택 후 이름 바꾸기를 해봤는데, 결과가 “여행사진 (1)”, “여행사진 (2)” 이런 식이라 영 맘에 안 들더라고요. 그래서 파이썬 스크립트를 직접 짜봤고, 시행착오를 좀 겪은 끝에 지금은 어떤 폴더든 30초면 정리가 끝나요. 그 과정에서 알게 된 것들을 정리해봤어요.

파일 이름 하나하나 바꾸다 포기한 날

사진 정리의 고통은 파일이 100개를 넘는 순간 시작돼요. 마우스 우클릭 → 이름 바꾸기 → 타이핑 → 엔터. 이 과정을 100번 반복한다고 생각해보면 벌써 손목이 아프죠. 제 경우엔 일본 여행 사진 430장이 방아쇠였어요.

문제는 단순 반복만이 아니었어요. 카메라마다 파일명 규칙이 다르니까 시간순 정렬도 안 되고, 확장자가 JPG인 것과 jpg인 것이 섞여 있어서 검색도 제대로 안 걸렸거든요. 거기에 카카오톡으로 받은 사진은 “KakaoTalk_20240315_142301234.jpg” 같은 긴 이름이 붙어 있었고요.

윈도우 기본 일괄 이름 바꾸기 기능은 사실 쓸 만한데, 넘버링 형식을 커스터마이징할 수 없다는 게 치명적이에요. “오사카_001.jpg” 같은 형태가 아니라 “오사카 (1).jpg”로 바뀌니까요. 괄호 안의 공백 때문에 나중에 코드에서 불러올 때도 골치 아파지더라고요.

그래서 결국 파이썬을 꺼냈어요. 10줄짜리 스크립트 하나로 430장 파일 이름을 2초 만에 바꿨을 때의 그 쾌감은 아직도 생생해요.

os.rename 기본 원리와 첫 번째 실수

파이썬에서 파일 이름을 바꾸는 가장 기본적인 방법은 os.rename() 함수예요. 첫 번째 인자에 원래 파일 경로, 두 번째 인자에 바꿀 파일 경로를 넣으면 끝이에요. import os 한 줄이면 바로 쓸 수 있어서 진입장벽이 거의 없죠.

근데 제가 첫 번째로 저지른 실수가 뭐였냐면, 전체 경로 없이 파일명만 넣은 거예요. os.rename(“IMG_001.jpg”, “여행_001.jpg”)만 적으면 현재 작업 디렉터리 기준으로 동작하거든요. 스크립트 파일이 바탕화면에 있고, 사진은 다른 폴더에 있었으니 당연히 FileNotFoundError가 떴죠. os.path.join()으로 폴더 경로와 파일명을 합쳐줘야 한다는 걸 그때 처음 알았어요.

기본 구조는 이래요. os.listdir()로 폴더 안 파일 목록을 가져오고, for 반복문으로 하나씩 돌리면서 os.rename()을 호출하는 거예요. 정렬이 필요하면 sorted()를 감싸주면 되고, 이미지 파일만 걸러내려면 파일명 끝이 .jpg, .png, .webp인지 확인하는 조건문을 넣으면 돼요.

⚠️ 주의

os.rename()은 같은 이름의 파일이 이미 존재할 경우, 리눅스/맥에서는 경고 없이 덮어쓰기를 해요. 윈도우에서는 FileExistsError가 발생하고요. 운영체제에 따라 동작이 다르기 때문에, 스크립트 실행 전에 반드시 중복 체크 로직을 넣어야 해요. 저도 이것 때문에 사진 3장을 날린 적이 있어요.

두 번째 실수는 확장자 처리였어요. 파일명을 바꿀 때 확장자까지 통째로 바꿔버려서 “.jpg”가 사라진 적이 있거든요. os.path.splitext()를 쓰면 파일명과 확장자를 깔끔하게 분리할 수 있어요. 이걸 모르고 문자열 슬라이싱으로 처리하려다가 “.jpeg” 같은 4글자 확장자에서 버그가 났었어요.

pathlib vs os 모듈, 뭐가 다른 건지 직접 비교

파이썬 3.4부터 추가된 pathlib 모듈은 파일 경로를 객체로 다뤄요. 처음엔 “os 모듈로 다 되는데 왜 또 새로운 걸 배워야 하지?” 싶었거든요. 근데 실제로 써보니까 코드가 확실히 깔끔해져요.

가장 큰 차이는 경로 결합 방식이에요. os 모듈에서는 os.path.join(folder, filename) 이렇게 함수를 호출하는데, pathlib에서는 Path(folder) / filename 이렇게 슬래시 연산자로 연결해요. 읽기가 훨씬 직관적이죠. 파일명에서 확장자만 빼고 싶을 때도 path.stem 한 줄이면 끝이에요. os.path.splitext(filename)[0] 같은 복잡한 인덱싱이 필요 없거든요.

비교 항목 os 모듈 pathlib 모듈
경로 결합 os.path.join(a, b) Path(a) / b
확장자 분리 os.path.splitext(f)[1] path.suffix
파일명만 추출 os.path.splitext(f)[0] path.stem
이름 변경 os.rename(old, new) path.rename(new)
글로브 패턴 glob.glob(“*.jpg”) path.glob(“*.jpg”)

제가 내린 결론은 이래요. 단순한 일회성 작업이면 os 모듈이 빠르고, 코드를 재사용하거나 유지보수할 계획이면 pathlib이 낫다는 거예요. 특히 pathlib의 with_stem() 메서드는 확장자는 유지한 채 파일명만 바꿀 수 있어서, 이미지 파일 이름 변경에 딱이에요. 파이썬 3.9 이상에서 쓸 수 있거든요.

한 가지 함정이 있는데, pathlib의 Path.rename()도 내부적으로는 os.rename()을 호출해요. 그래서 덮어쓰기 문제는 똑같이 발생하고요. 다른 드라이브(예: C: → D:)로 이동하면서 이름을 바꾸려면 shutil.move()를 써야 한다는 점도 동일해요.

실전 스크립트 작성 — 날짜순 넘버링부터 특수문자 제거까지

실제로 제가 쓰는 스크립트는 크게 세 가지 기능이에요. 첫째, 지정 폴더의 이미지 파일만 걸러내기. 둘째, 원하는 접두어 + 넘버링으로 이름 바꾸기. 셋째, 파일명에 포함된 특수문자나 공백을 안전하게 제거하기.

접두어 + 넘버링 방식이 가장 많이 쓰여요. “오사카_001.jpg”, “오사카_002.jpg” 이런 식으로요. 핵심은 zfill() 메서드인데, 넘버링 자릿수를 맞춰줘요. 파일이 100개 이상이면 zfill(3), 1000개 이상이면 zfill(4)를 쓰면 되거든요. 이걸 안 하면 1 다음에 10이 와서 정렬 순서가 엉망이 됩니다.

💡 꿀팁

파일명에서 특수문자를 제거할 때 정규표현식 re.sub()를 쓰면 깔끔해요. re.sub(r'[\\/:*?”<>|]’, ”, filename) 한 줄이면 윈도우에서 파일명에 쓸 수 없는 문자 8종을 한 번에 날릴 수 있거든요. 공백은 언더스코어로 바꾸는 게 나중에 터미널에서 다룰 때도 편해요.

sorted() 함수로 정렬할 때 주의할 점이 하나 있어요. 기본 정렬은 문자열 기준이라 “IMG_2.jpg”가 “IMG_10.jpg”보다 뒤에 오거든요. 숫자 부분만 추출해서 int로 변환한 키 함수를 넣어줘야 자연스러운 순서가 돼요. 이걸 자연 정렬(natural sort)이라고 부르는데, 처음에 이거 몰라서 넘버링이 뒤섞인 적이 있었어요.

확장자를 통일하고 싶을 때도 있잖아요. JPG, Jpg, jpeg이 혼재되어 있으면 .lower()로 소문자 통일하고, “.jpeg”은 “.jpg”로 치환하는 로직을 한 줄 추가하면 돼요. 사소한 것 같지만, 이게 나중에 웹에 업로드할 때 파일 타입 필터링에서 걸리는 경우가 있어서 미리 해두는 게 좋아요.

EXIF 촬영일 기준으로 파일명 자동 정리하기

여기서 한 단계 더 나가면 EXIF 데이터를 활용할 수 있어요. EXIF는 사진 파일에 내장된 메타데이터인데, 촬영 날짜, 카메라 모델, GPS 좌표 같은 정보가 들어 있거든요. Pillow 라이브러리의 Image 모듈과 ExifTags 모듈을 쓰면 파이썬에서 이 정보를 읽을 수 있어요.

pip install Pillow 한 번이면 설치 끝이에요. Image.open()으로 사진을 열고, _getexif() 메서드를 호출하면 딕셔너리 형태로 EXIF 데이터가 나와요. 여기서 키 값 36867이 “DateTimeOriginal”인데, 이게 실제 촬영 시각이에요. 형식이 “2024:03:15 14:23:01″처럼 나오니까, 콜론을 하이픈으로 바꾸고 공백을 언더스코어로 치환하면 바로 파일명으로 쓸 수 있죠.

근데 한 가지 문제가 있었어요. 카카오톡이나 스크린샷 이미지는 EXIF 정보가 아예 없는 경우가 많거든요. _getexif()가 None을 반환하면 스크립트가 터져요. 그래서 EXIF가 없으면 파일의 수정 날짜(os.path.getmtime)를 대체값으로 쓰는 분기 처리를 반드시 넣어야 해요. 저는 이걸 빠뜨려서 스크립트가 중간에 멈추고, 이미 바뀐 파일과 안 바뀐 파일이 섞이는 혼란을 겪었어요.

📊 실제 데이터

제 테스트 기준, 이미지 1,200장(총 4.8GB)의 EXIF를 읽고 파일명을 변경하는 데 걸린 시간은 약 8초였어요. 같은 작업을 수동으로 하면 최소 3시간은 잡아야 하거든요. Pillow가 이미지를 완전히 디코딩하지 않고 헤더만 읽기 때문에 생각보다 빨라요.

촬영일 기준 정리의 최종 형태는 “20240315_142301_001.jpg” 같은 포맷이에요. 날짜 + 시각 + 동일 초 내 넘버링. 같은 초에 연사로 찍은 사진이 있을 수 있으니까 뒤에 넘버링을 붙이는 게 안전하거든요. 이렇게 하면 탐색기에서 이름순 정렬만 해도 시간순으로 사진이 쭉 나와요.

중복·덮어쓰기 사고 막는 안전장치 넣기

파일 이름 일괄 변경에서 가장 무서운 건 되돌릴 수 없다는 거예요. os.rename()에는 undo 기능이 없거든요. 그래서 저는 몇 번 사고를 치고 나서 세 가지 안전장치를 만들었어요.

첫 번째는 드라이런(dry run) 모드예요. 실제로 파일명을 바꾸기 전에 “원래 이름 → 바뀔 이름”을 print로 출력만 하는 거예요. 변수 하나(DRY_RUN = True)만 두고, if not DRY_RUN: 조건 안에서만 os.rename()을 실행하면 돼요. 이게 별것 아닌 것 같은데, 실수를 미리 잡아주는 효과가 엄청나요.

두 번째는 중복 파일명 자동 처리예요. 바꾸려는 이름이 이미 존재하면 뒤에 “_1”, “_2″를 붙이는 while 루프를 넣어요. os.path.exists()로 확인하면서 숫자를 올리는 간단한 로직인데, 이것만 있으면 덮어쓰기 사고를 100% 막을 수 있어요.

세 번째는 변경 로그 저장이에요. 변경 전후 파일명을 CSV 파일로 남겨두면 나중에 원래 이름으로 되돌리는 것도 가능하거든요. csv 모듈로 한 줄씩 쓰면 되니까 코드도 길지 않아요. 이 로그 파일 덕분에 실제로 한 번 롤백한 적이 있는데, 그때 진짜 만들어두길 잘했다 싶었어요.

💬 직접 써본 경험

shutil.copy2()로 원본을 백업 폴더에 복사한 뒤 이름을 바꾸는 방식도 써봤는데, 파일이 많으면 디스크 용량을 두 배로 먹어서 현실적이지 않았어요. 결국 드라이런 + 중복 체크 + CSV 로그 조합이 가장 가볍고 안전하더라고요. 특히 로그 파일로 롤백하는 역방향 스크립트를 하나 더 만들어두면 마음이 편해져요.

한 가지 더 팁을 드리면, glob 패턴으로 이미지 확장자를 필터링할 때 대소문자를 주의해야 해요. glob.glob(“*.jpg”)는 “.JPG”를 잡아내지 못하거든요. 리눅스에서는 대소문자를 구분하니까요. 그래서 저는 glob 대신 pathlib의 Path.iterdir()로 전체 파일을 가져온 뒤, suffix.lower()로 확장자를 소문자 변환해서 필터링하는 방식을 쓰고 있어요.

정규표현식으로 파일명 패턴을 분석하는 것도 유용해요. re.match(r'(\d{8})_(\d{6})’, filename) 같은 패턴으로 이미 날짜 형식인 파일은 건너뛰게 만들 수 있거든요. 이미 정리된 파일까지 또 바꾸면 이중 넘버링이 붙는 참사가 벌어지니까요.

자주 묻는 질문

Q. 파이썬을 설치하지 않고도 스크립트를 실행할 수 있나요?

PyInstaller로 .exe 파일로 변환하면 파이썬 없이도 실행할 수 있어요. pyinstaller –onefile rename_script.py 명령어 한 줄이면 독립 실행 파일이 만들어져요. 다만 EXIF 기능을 쓰려면 Pillow가 함께 패키징되어야 하니까 파일 크기가 좀 커질 수 있어요.

Q. PNG, WEBP 같은 포맷도 EXIF 정보가 있나요?

PNG는 기본적으로 EXIF를 지원하지 않고, 대신 tEXt 청크에 메타데이터를 저장해요. WEBP는 EXIF를 포함할 수 있지만 모든 프로그램이 기록하지는 않아요. 안전하게 가려면 EXIF 실패 시 파일 수정일을 대체로 쓰는 분기 처리가 필수예요.

Q. 하위 폴더까지 재귀적으로 처리할 수 있나요?

pathlib의 rglob(“*.jpg”)를 쓰면 하위 폴더까지 재귀적으로 탐색해요. os 모듈에서는 os.walk()가 같은 역할을 하고요. 다만 하위 폴더에서 같은 이름의 파일이 나올 수 있으니 폴더 경로를 파일명에 포함시키거나 중복 체크를 더 엄격하게 해야 해요.

Q. 한글 파일명이 포함되면 오류가 나나요?

파이썬 3에서는 기본적으로 유니코드를 지원하기 때문에 한글 파일명도 문제없이 처리돼요. 다만 OpenCV(cv2) 같은 일부 라이브러리는 한글 경로를 인식하지 못하는 경우가 있어서, 이미지 처리까지 할 거면 영문 경로를 쓰는 게 안전해요.

Q. 이미 바꾼 파일 이름을 원래대로 되돌릴 수 있나요?

변경 전후 매핑을 CSV로 저장해뒀다면, 역방향으로 os.rename(new, old)를 실행하는 롤백 스크립트를 만들 수 있어요. 로그 없이 바꿨다면 복구가 불가능하니까, 드라이런과 로그 저장을 습관처럼 하는 게 좋아요.

본 포스팅은 개인 경험과 공개 자료를 바탕으로 작성되었으며, 전문적인 의료·법률·재무 조언을 대체하지 않습니다. 정확한 정보는 해당 분야 전문가 또는 공식 기관에 확인하시기 바랍니다.

파일 이름 일괄 변경은 os.rename() 한 줄로 시작하지만, 안전하게 쓰려면 드라이런·중복 체크·CSV 로그라는 세 가지 안전장치가 꼭 필요해요. 여기에 pathlib과 Pillow를 조합하면 EXIF 촬영일 기반 자동 정리까지 가능하고요.

코딩 초보라면 os 모듈 + 넘버링 스크립트부터 시작해보세요. 사진 정리가 일상인 분이라면 EXIF 기반 스크립트를 한 번 만들어두면 두고두고 써먹을 수 있어요. 수동 작업이 싫은 분이라면 오늘 당장 10줄짜리 스크립트부터 돌려보시길 추천해요.


직접 스크립트를 돌려보셨다면 어떤 방식으로 파일명을 정리했는지 댓글로 공유해주세요. 비슷한 고민을 가진 분들에게 큰 도움이 됩니다.

댓글 남기기