폴더에 있는 사진/동영상을 rename 하는 방법
기존에는 사진편집기를 이용해서 rename 했었는데 NAS에 있는 20년치 사진 동영상을
한꺼번에 rename 하려다 보니 사진편집기로는 너무 느려서 python으로 하니 수백배? 수천배? 빨리진거 같네요
[%Y:%m:%d_%H:%M:%S_일련번호3자리] 형식으로 변환 합니다.
예) 2025-01-11_134006_001.JPG
import os
from datetime import datetime, timedelta
from PIL import Image
import exifread
import imghdr
import mimetypes
import time
import sys
import contextlib
from io import StringIO
import rawpy
from pillow_heif import register_heif_opener
# HEIC 파일 지원 등록
register_heif_opener()
# 파일명 생성 함수
# 타임스탬프와 인덱스, 확장자를 받아 새로운 파일명을 생성합니다.
def generate_new_filename(timestamp, index, extension):
return f"{timestamp}_{index:03d}.{extension.upper()}"
# EXIF에서 촬영 시간 추출 함수
# EXIF 데이터에서 촬영 시간을 추출합니다. 실패하면 None을 반환합니다.
def extract_timestamp(file_path):
extension = file_path.split('.')[-1].upper()
try:
if extension in ['ARW', 'CR2', 'CR3', 'DNG']:
with rawpy.imread(file_path) as raw:
metadata = raw.metadata
if metadata.timestamp:
return metadata.timestamp
elif extension in ['HEIC', 'HEIF', 'PNG']:
with contextlib.redirect_stderr(StringIO()): # 표준 오류 억제
image = Image.open(file_path)
exif_data = image.getexif()
date_taken = exif_data.get(36867) # DateTimeOriginal
if date_taken:
return datetime.strptime(date_taken, "%Y:%m:%d %H:%M:%S")
else:
with open(file_path, 'rb') as f:
tags = exifread.process_file(f, stop_tag="EXIF DateTimeOriginal")
date_taken = tags.get("EXIF DateTimeOriginal")
if date_taken:
return datetime.strptime(str(date_taken), "%Y:%m:%d %H:%M:%S")
except Exception:
# 오류 메시지를 표시하지 않고 None 반환
return None
return None
# 진행률 표시 함수
# 처리된 파일 수와 총 파일 수, 경과 시간, 예상 완료 시간을 기반으로 진행 상태를 표시합니다.
def print_progress_bar(processed, total, start_time):
bar_length = 100 # 진행 바의 총 길이
progress = processed / total # 진행 비율 계산
filled_length = int(bar_length * progress) # 채워질 진행 바 길이
bar = "#" * filled_length + "-" * (bar_length - filled_length)
# 경과 시간 및 남은 시간 계산
elapsed_time = time.time() - start_time
estimated_total_time = elapsed_time / progress if progress > 0 else 0
remaining_time = estimated_total_time - elapsed_time
# 시간 형식으로 변환
elapsed_str = str(timedelta(seconds=int(elapsed_time)))
remaining_str = str(timedelta(seconds=int(remaining_time)))
# 이전 출력 덮어쓰기
sys.stdout.write("\033[2K\033[F" * 4) # 이전 4줄을 지우고 위로 이동
sys.stdout.write(f"[{bar}] {progress * 100:.2f}%\n")
sys.stdout.write(f"진행건수 : {processed}/{total}\n")
sys.stdout.write(f"진행시간 : {elapsed_str}\n")
sys.stdout.write(f"남은시간 : {remaining_str}\n")
sys.stdout.flush()
# 파일 처리 함수
# 지정된 디렉토리 내 파일을 순회하며 파일명을 변경하고 상태를 업데이트합니다.
def process_files(input_dir):
timestamps = {} # 파일명 중복 방지를 위한 타임스탬프별 카운터
total_files = 0 # 전체 파일 수
image_files = 0 # 이미지 파일 수
video_files = 0 # 비디오 파일 수
other_files = 0 # 기타 파일 수
# 전체 파일 수 계산
for root, _, files in os.walk(input_dir):
total_files += len(files)
processed_files = 0 # 처리된 파일 수
start_time = time.time() # 처리 시작 시간
for root, _, files in os.walk(input_dir):
for file in files:
file_path = os.path.join(root, file)
# 숨겨진 파일 무시
if file.startswith('.'):
continue
# 파일 존재 여부 확인
if not os.path.exists(file_path):
continue
processed_files += 1
extension = file.split('.')[-1].upper() if '.' in file else ''
# 이미지 파일인지 확인
try:
with contextlib.redirect_stderr(StringIO()): # 표준 오류 출력 억제
is_image = imghdr.what(file_path)
if is_image or extension in ['ARW', 'CR2', 'CR3', 'HEIC', 'DNG']:
image_files += 1
elif mimetypes.guess_type(file_path)[0] and mimetypes.guess_type(file_path)[0].startswith('video'):
video_files += 1
else:
other_files += 1
except Exception:
other_files += 1
timestamp = extract_timestamp(file_path)
if not timestamp:
# EXIF 정보를 가져올 수 없는 경우 파일 수정 시간 사용
timestamp = datetime.fromtimestamp(os.path.getmtime(file_path))
formatted_timestamp = timestamp.strftime("%Y-%m-%d_%H%M%S")
if formatted_timestamp not in timestamps:
timestamps[formatted_timestamp] = 1
else:
timestamps[formatted_timestamp] += 1
new_filename = generate_new_filename(formatted_timestamp, timestamps[formatted_timestamp], extension)
new_file_path = os.path.join(root, new_filename)
try:
os.rename(file_path, new_file_path)
except Exception:
pass # 예외 발생 시 아무것도 출력하지 않음
# 10건 마다 진행률 출력
if processed_files % 10 == 0 or processed_files == total_files:
print_progress_bar(processed_files, total_files, start_time)
# 처리 완료 후 요약 출력
print("\n파일명 변환완료")
print(f"대상폴더 : {input_dir}")
print(f"총 건수 : {total_files:,}")
print(f"이미지 파일 : {image_files:,}")
print(f"비디오 파일 : {video_files:,}")
print(f"기타 파일 : {other_files:,}")
# 메인 함수
def main():
input_dir = input("변환 할 폴더명을 입력해 주세요 /Volumes/photo/2024 : ")
if not os.path.exists(input_dir):
print("입력한 폴더가 존재하지 않습니다.")
return
process_files(input_dir)
# 프로그램 시작 지점
if __name__ == "__main__":
main()