08
업무 자동화 ② 사진 (2) — 폴더째 돌리고 워터마크를 찍는다
앞 강의에서 사진 한 장을 열고 줄였다. 백 장도 똑같다. 그 세 줄을 반복문에 넣으면 끝난다.
파일은 계속 ai-course 폴더에 넣는다. 이번 강의는 81번부터다. 앞 강의에서 만든 사진 폴더가 그대로 있어야 한다.
1. 폴더째 일괄 처리 — 여기가 이번 강의의 전부
앞 강의의 세 줄을 반복문에 넣기만 하면 된다.
import os
import glob
from PIL import Image
os.makedirs("사진_작게", exist_ok=True)
for path in sorted(glob.glob("사진/*.jpg")):
img = Image.open(path) # 연다
img.thumbnail((800, 800)) # 바꾼다
img.save(f"사진_작게/{os.path.basename(path)}", quality=85) # 저장한다
# 얼마나 줄었나 재본다
before = sum(os.path.getsize(p) for p in glob.glob("사진/*.jpg"))
after = sum(os.path.getsize(p) for p in glob.glob("사진_작게/*.jpg"))
print(f"{before/1024:.0f} KB -> {after/1024:.0f} KB")
print(f"{(1 - after/before) * 100:.0f}% 줄어듦")
print(Image.open("사진_작게/행사_001.jpg").size)
# 원본은 그대로인지 확인한다. 이게 습관이 되어야 한다
print(f"원본 {before/1024:.0f} KB", Image.open("사진/행사_001.jpg").size)

사진 3장에 눈 깜짝할 새다. 100장이면 5초 안팎이다. 그리고 마지막 줄이 중요하다 — 원본 폴더는 손대지 않았다.

2. 워터마크를 찍는다
사진 위에 글자를 얹는다. 아래쪽에 검은 띠를 깔고 그 위에 글자를 쓴다.
import os
import glob
from PIL import Image, ImageDraw
os.makedirs("사진_완성", exist_ok=True)
for path in sorted(glob.glob("사진/*.jpg")):
img = Image.open(path)
img.thumbnail((800, 800))
draw = ImageDraw.Draw(img) # 이 사진 위에 그릴 붓
# 아래쪽 30픽셀에 검은 띠. (왼쪽, 위, 오른쪽, 아래) 순서다
draw.rectangle((0, img.height - 30, img.width, img.height), fill=(0, 0, 0))
# 그 위에 흰 글자. (x, y) 는 글자가 시작할 왼쪽 위 지점
draw.text((10, img.height - 22), "(C) DCODELAB 2026", fill=(255, 255, 255))
img.save(f"사진_완성/{os.path.basename(path)}", quality=85)
총합 = sum(os.path.getsize(p) for p in glob.glob("사진_완성/*.jpg"))
print(f"{총합/1024:.1f} KB")

ai-course/사진_완성 폴더를 열어 확인한다. 아래쪽에 검은 띠와 글자가 찍혀 있다.

3. 함수로 묶는다
엑셀 때와 같다. 폴더 이름과 크기를 인자로 뺀다.
import os
import glob
from PIL import Image, ImageDraw
def 사진_일괄(src, dst, size=800, mark=None, quality=85):
"""src 폴더의 사진을 줄여 dst 폴더에 저장한다. mark 를 주면 워터마크를 찍는다."""
os.makedirs(dst, exist_ok=True)
paths = sorted(glob.glob(f"{src}/*.jpg")) + sorted(glob.glob(f"{src}/*.png"))
if not paths:
print(f"[경고] '{src}' 에서 사진을 못 찾았다.")
return 0
before = after = done = 0
for path in paths:
try:
img = Image.open(path)
except Exception as e: # 깨진 파일 · 사진이 아닌 파일
print(f"[건너뜀] {path} - {type(e).__name__}")
continue
done += 1 # 찾은 수가 아니라 처리한 수를 센다
before += os.path.getsize(path)
img.thumbnail((size, size))
if mark:
draw = ImageDraw.Draw(img)
draw.rectangle((0, img.height - 30, img.width, img.height), fill=(0, 0, 0))
draw.text((10, img.height - 22), mark, fill=(255, 255, 255))
out = f"{dst}/{os.path.basename(path)}"
img.save(out, quality=quality)
after += os.path.getsize(out)
print(f"{done}장 처리 · {before/1024:.0f} KB -> {after/1024:.0f} KB "
f"({(1 - after/before) * 100:.0f}% 줄어듦)")
return done
사진_일괄("사진", "사진_완성", size=800, mark="(C) DCODELAB 2026")
사진_일괄("없는폴더", "결과")


