사진을 업(業)으로 하시는 분이 아니라면, 촬영한 사진을 개인적인 기록으로 보관하는 것 외에
특별히 활용하기는 어렵습니다.
그래도 그중에서 잘 찍은 사진이 있으면 지인이나 SNS를 통해 공개를 하고 싶은 욕구가 생기게 되죠.
제 경우에는 수직으로 찍은 사진은 릴스(동영상)로 만들어서 인스타그램에 올립니다.
수평으로 찍은 사진은 아직은 따로 활용을 못하고 있는데,
앞으로 네이버에 전용 블로그를 하나 만들어서 올려 볼 생각입니다.
이런 블로그용 사진에는 사진 하단에 사진에 관한 정보를 같이 기입해 주는 것이 일종의 예의입니다.
이 정보를 EXiF (Exchangeable Image File format) 메타데이터라고 하는데,
여기에는 카메라 정보, 렌즈 정보, 카메라 설정 (초점거리, ISO 감도, 조리개, 셔터속도,,,),
아티스트 및 저작권 정보, 위치 정보 등이 들어 있습니다.
사진 하단에 EXiF 정보를 표시하는 방법은 여러 가지가 있는데
대표적으로 라이트룸, 포토샵 등과 같은 프로그램을 활용하면 비교적 손쉽게 구현이 가능합니다.
물론, EXiF 정보 표시에 특화된 앱들도 있고요...
그런데,,,,, 문제는 모두 유료라는 점입니다.ㅠㅠ
그래서 몇 가지 자료를 조사해 보고, 이것을 파이썬 코딩을 통해 구현하기로 했습니다.
결과물은 아래 사진과 같습니다.
하단에 흰색 여백을 추가하고, 왼쪽 상단에는 ISO 감도, 초점거리, 조리개, 셔터 속도를 표시하고
하단에는 촬영 날짜와 시간, 그리고 오른쪽에는 카메라 정보와 렌즈 정보를 기록하도록 만들었습니다.

입력과 출력 폴더를 아래와 같이 구성했습니다.
- 입력 폴더: D:\10_picture\origial
- 출력 폴더: D:\10_picture\origial\new_generated
입력 폴더에 원본 JPEG를 복사해 두고 스크립트를 실행하면,
원본 JPEG 사진을 블로그용으로 적당한 가로 1600px 크기로 리사이즈를 하고,
하단에 EXiF 바가 표함 된 결과물이 출력 폴더에 자동 생성됩니다.
리사이즈 단계에서 화질이 유지되도록 신경을 썼습니다. 아래는 파이썬 스크립트의 동작 개요입니다.
1. 입력 폴더에서 JPG/JPEG 파일을 모두 탐색합니다.
2. EXIF Orientation을 반영해 가로/세로 방향을 실제 촬영 상태와 동일하게 보정합니다.
3. 가로길이 1600px 기준으로 고품질 LANCZOS 리사이즈를 수행합니다.
4. UnsharpMask 필터를 적용해 축소 과정에서 잃어버린 선명도를 자연스럽게 복원합니다.
5. 이미지 하단에 96px의 흰색 여백을 추가하여 EXIF 정보를 넣을 바 영역을 만듭니다.
6. 좌측에는 ISO / 초점거리 / 조리개값 / 셔터스피드 / 촬영일시를, 우측에는 카메라 기종과
렌즈명을 정렬해 배치합니다.
7. JPEG 품질 98, 4:4:4(subsampling=0) 설정으로 색 번짐을 최소화한 고화질 파일로
저장합니다.
Python 3.11 + Pillow 최신 버전(Pillow 10.x) 환경에서 실행
실행 소스가 들어있는 폴더에 폰트 파일인 'NotoSansKR-Regular.ttf'을 같이 둘 것
import os
from PIL import Image, ImageDraw, ImageFont, ImageOps, ImageFilter
import piexif
# ----------- 폴더 경로 설정 -----------
INPUT_DIR = r"D:\10_picture\origial"
OUTPUT_DIR = r"D:\10_picture\origial\new_generated"
FONT_PATH = "NotoSansKR-Regular.ttf" # 폰트 파일 (.ttf) 저장 필요
FONT_SIZE = 28
LINE_SPACING = -25
BOTTOM_MARGIN = 96 # 하단 여백(px)
TARGET_WIDTH = 1600 # 리사이즈 기준 가로폭
os.makedirs(OUTPUT_DIR, exist_ok=True)
# ============================================================
# EXIF 정보 읽기
# ============================================================
def load_exif(img_path):
exif_dict = piexif.load(img_path)
exif = exif_dict.get("Exif", {})
zeroth = exif_dict.get("0th", {})
iso = exif.get(piexif.ExifIFD.ISOSpeedRatings)
focal = exif.get(piexif.ExifIFD.FocalLength, (0, 1))
fnum = exif.get(piexif.ExifIFD.FNumber, (0, 1))
shutter = exif.get(piexif.ExifIFD.ExposureTime, (0, 1))
model = zeroth.get(piexif.ImageIFD.Model, b"").decode(errors="ignore")
lens = exif.get(piexif.ExifIFD.LensModel, b"").decode(errors="ignore")
datetime = zeroth.get(piexif.ImageIFD.DateTime, b"").decode(errors="ignore")
return {
"ISO": iso,
"FocalLength": round(focal[0] / focal[1], 1),
"FNumber": round(fnum[0] / fnum[1], 1),
"Shutter": f"{shutter[0]}/{shutter[1]}s",
"Camera": model,
"Lens": lens,
"DateTime": datetime,
}
# ============================================================
# 텍스트 길이 계산 (Pillow 10 대응)
# ============================================================
def text_width(draw, text, font):
bbox = draw.textbbox((0, 0), text, font=font)
return bbox[2] - bbox[0]
# ============================================================
# 이미지 처리 함수
# ============================================================
def process_image(filename):
img_path = os.path.join(INPUT_DIR, filename)
exif_data = load_exif(img_path)
# ---- 1) EXIF Orientation 자동 반영 (세로 사진 보정) ----
img = Image.open(img_path)
img = ImageOps.exif_transpose(img)
w, h = img.size
# ---- 2) 리사이즈 (길이 1600 기준 / 화질 최고) ----
ratio = TARGET_WIDTH / w
new_w = TARGET_WIDTH
new_h = int(h * ratio)
img = img.resize((new_w, new_h), Image.LANCZOS)
# ---- 3) 선명도 보정 (UnsharpMask) ----
img = img.filter(ImageFilter.UnsharpMask(radius=1.0, percent=50, threshold=3))
# ---- 4) 하단 여백 추가 ----
canvas = Image.new("RGB", (new_w, new_h + BOTTOM_MARGIN), (255, 255, 255))
canvas.paste(img, (0, 0))
draw = ImageDraw.Draw(canvas)
font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
# ===================== 텍스트 배치 =====================
# 좌측 상단
left_top = (
f"ISO {exif_data['ISO']} "
f"{exif_data['FocalLength']}mm "
f"F{exif_data['FNumber']} "
f"{exif_data['Shutter']}"
)
draw.text((30, new_h + 10), left_top, fill=(0, 0, 0), font=font)
# 좌측 하단: 날짜
draw.text(
(30, new_h + 10 + FONT_SIZE * 2 + LINE_SPACING),
exif_data["DateTime"],
fill=(0, 0, 0),
font=font,
)
# 우측 상단
camera_text = exif_data["Camera"]
cam_w = text_width(draw, camera_text, font)
draw.text(
(new_w - cam_w - 30, new_h + 10),
camera_text,
fill=(0, 0, 0),
font=font,
)
# 우측 하단: 렌즈
lens_text = exif_data["Lens"]
lens_w = text_width(draw, lens_text, font)
draw.text(
(new_w - lens_w - 30, new_h + 10 + FONT_SIZE * 2 + LINE_SPACING),
lens_text,
fill=(0, 0, 0),
font=font,
)
# ---- 5) Orientation 태그 초기화 ----
try:
exif_dict = piexif.load(img_path)
exif_dict["0th"][piexif.ImageIFD.Orientation] = 1
exif_bytes = piexif.dump(exif_dict)
except:
exif_bytes = None
# ---- 6) 저장 (최고 화질: 4:4:4 + Q98) ----
name, _ = os.path.splitext(filename)
out_path = os.path.join(OUTPUT_DIR, f"{name}_exif.jpg")
canvas.save(
out_path,
quality=98,
subsampling=0, # 4:4:4 (색 보존 최고)
exif=exif_bytes,
)
print("완료:", filename)
# ============================================================
# 메인 실행
# ============================================================
def main():
for file in os.listdir(INPUT_DIR):
if file.lower().endswith((".jpg", ".jpeg")):
process_image(file)
if __name__ == "__main__":
main()
이렇게 프로그램을 만들어 실행하면, 장점이 실행 속도입니다.
수십 장의 사진을 넣어도 순식간에 파일 사이즈를 1600px로 바꾸고, EXiF 바를 추가해 주므로 손으로 작업할 때에 비해서 간편하고 시간이 많이 절약됩니다.
물론, 무료라는 점은 기본이죠^^.
감사합니다.