image-batch-processor
이미지 배치 처리 — 리사이즈, 워터마크, 포맷 변환, 압축, 썸네일 생성, EXIF 제거 등 여러 이미지를 한번에 처리
이미지 배치 처리기
여러 이미지를 한번에 리사이즈, 변환, 워터마크, 압축 등 처리하는 스킬. Python Pillow와 ImageMagick을 활용합니다.
언제 사용할까
- "이 사진들 전부 리사이즈해줘"
- "워터마크 추가해줘"
- "이미지들 WebP로 변환해줘"
- "썸네일 만들어줘"
- "EXIF 정보 지워줘"
전제 조건
- Python 3.10+ 와 pip
- 필요한 패키지: Pillow (자동 설치)
- ImageMagick (선택,
brew install imagemagick)
1. 리사이즈
단일 크기로 리사이즈
from PIL import Image
import os, globINPUT_DIR = "/path/to/images"
OUTPUT_DIR = "/path/to/output"
TARGET_SIZE = (800, 600) # (width, height)
os.makedirs(OUTPUT_DIR, exist_ok=True)
for path in glob.glob(os.path.join(INPUT_DIR, "*")):
ext = os.path.splitext(path)[1].lower()
if ext not in ('.jpg', '.jpeg', '.png', '.webp', '.bmp', '.tiff'):
continue
img = Image.open(path)
# 비율 유지하면서 리사이즈
img.thumbnail(TARGET_SIZE, Image.Resampling.LANCZOS)
out_path = os.path.join(OUTPUT_DIR, os.path.basename(path))
img.save(out_path, quality=90)
print(f"✅ {os.path.basename(path)} → {img.size}")
print(f"완료! {len(glob.glob(os.path.join(OUTPUT_DIR, '*')))}개 처리됨")
비율 기준 리사이즈
# 최대 너비/높이 제한 (비율 유지)
MAX_WIDTH = 1200
MAX_HEIGHT = 1200for path in glob.glob(os.path.join(INPUT_DIR, "*")):
img = Image.open(path)
img.thumbnail((MAX_WIDTH, MAX_HEIGHT), Image.Resampling.LANCZOS)
# ...저장
배율 기준 리사이즈
SCALE = 0.5 # 50% 축소for path in glob.glob(os.path.join(INPUT_DIR, "*")):
img = Image.open(path)
new_size = (int(img.width SCALE), int(img.height SCALE))
img = img.resize(new_size, Image.Resampling.LANCZOS)
# ...저장
2. 포맷 변환
from PIL import Image
import os, globINPUT_DIR = "/path/to/images"
OUTPUT_DIR = "/path/to/output"
TARGET_FORMAT = "webp" # webp, png, jpeg, bmp
QUALITY = 85
os.makedirs(OUTPUT_DIR, exist_ok=True)
for path in glob.glob(os.path.join(INPUT_DIR, "*")):
ext = os.path.splitext(path)[1].lower()
if ext not in ('.jpg', '.jpeg', '.png', '.webp', '.bmp', '.tiff', '.gif'):
continue
img = Image.open(path)
# RGBA → RGB 변환 (JPEG 저장 시 필요)
if img.mode == 'RGBA' and TARGET_FORMAT == 'jpeg':
bg = Image.new('RGB', img.size, (255, 255, 255))
bg.paste(img, mask=img.split()[3])
img = bg
name = os.path.splitext(os.path.basename(path))[0]
out_path = os.path.join(OUTPUT_DIR, f"{name}.{TARGET_FORMAT}")
save_kwargs = {}
if TARGET_FORMAT == 'webp':
save_kwargs['quality'] = QUALITY
elif TARGET_FORMAT == 'jpeg':
save_kwargs['quality'] = QUALITY
save_kwargs['optimize'] = True
elif TARGET_FORMAT == 'png':
save_kwargs['optimize'] = True
img.save(out_path, **save_kwargs)
orig_size = os.path.getsize(path) / 1024
new_size = os.path.getsize(out_path) / 1024
print(f"✅ {os.path.basename(path)} ({orig_size:.0f}KB) → {name}.{TARGET_FORMAT} ({new_size:.0f}KB)")
3. 워터마크 추가
텍스트 워터마크
from PIL import Image, ImageDraw, ImageFont
import os, globINPUT_DIR = "/path/to/images"
OUTPUT_DIR = "/path/to/output"
WATERMARK_TEXT = "© 2024 Sample"
OPACITY = 128 # 0-255
POSITION = "bottom-right" # center, bottom-right, bottom-left, top-right, top-left
os.makedirs(OUTPUT_DIR, exist_ok=True)
for path in glob.glob(os.path.join(INPUT_DIR, "*")):
ext = os.path.splitext(path)[1].lower()
if ext not in ('.jpg', '.jpeg', '.png', '.webp'):
continue
img = Image.open(path).convert('RGBA')
overlay = Image.new('RGBA', img.size, (255, 255, 255, 0))
draw = ImageDraw.Draw(overlay)
# 폰트 크기 = 이미지 크기의 3%
font_size = max(12, min(img.width, img.height) // 30)
try:
font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", font_size)
except:
font = ImageFont.load_default()
bbox = draw.textbbox((0, 0), WATERMARK_TEXT, font=font)
text_w, text_h = bbox[2] - bbox[0], bbox[3] - bbox[1]
margin = max(10, min(img.width, img.height) // 30)
if POSITION == "bottom-right":
x, y = img.width - text_w - margin, img.height - text_h - margin
elif POSITION == "bottom-left":
x, y = margin, img.height - text_h - margin
elif POSITION == "top-right":
x, y = img.width - text_w - margin, margin
elif POSITION == "top-left":
x, y = margin, margin
else: # center
x, y = (img.width - text_w) // 2, (img.height - text_h) // 2
draw.text((x, y), WATERMARK_TEXT, font=font, fill=(255, 255, 255, OPACITY))
result = Image.alpha_composite(img, overlay)
result = result.convert('RGB')
out_path = os.path.join(OUTPUT_DIR, os.path.basename(path))
result.save(out_path, quality=95)
print(f"✅ 워터마크 추가: {os.path.basename(path)}")
이미지 워터마크 (로고)
WATERMARK_PATH = "/path/to/logo.png"
watermark = Image.open(WATERMARK_PATH).convert('RGBA')for path in glob.glob(os.path.join(INPUT_DIR, "*")):
img = Image.open(path).convert('RGBA')
# 워터마크 크기 = 이미지의 20%
wm_ratio = 0.2
new_wm_size = (int(img.width wm_ratio), int(img.height wm_ratio))
wm_resized = watermark.resize(new_wm_size, Image.Resampling.LANCZOS)
# 위치 계산 (오른쪽 하단)
x = img.width - wm_resized.width - 20
y = img.height - wm_resized.height - 20
# 투명도 설정
wm_resized.putalpha(180)
img.paste(wm_resized, (x, y), wm_resized)
result = img.convert('RGB')
out_path = os.path.join(OUTPUT_DIR, os.path.basename(path))
result.save(out_path, quality=95)
4. 이미지 압축
from PIL import Image
import os, globINPUT_DIR = "/path/to/images"
OUTPUT_DIR = "/path/to/output"
TARGET_QUALITY = 80 # 1-100
MAX_FILE_SIZE_KB = 200 # 최대 파일 크기 제한 (KB)
os.makedirs(OUTPUT_DIR, exist_ok=True)
for path in glob.glob(os.path.join(INPUT_DIR, "*")):
img = Image.open(path)
name = os.path.splitext(os.path.basename(path))[0]
# WebP로 변환하여 압축
out_path = os.path.join(OUTPUT_DIR, f"{name}.webp")
quality = TARGET_QUALITY
while quality >= 20:
img.save(out_path, 'webp', quality=quality, method=6)
size_kb = os.path.getsize(out_path) / 1024
if size_kb <= MAX_FILE_SIZE_KB or quality <= 20:
break
quality -= 10
orig_size = os.path.getsize(path) / 1024
new_size = os.path.getsize(out_path) / 1024
ratio = (1 - new_size / orig_size) * 100
print(f"✅ {os.path.basename(path)}: {orig_size:.0f}KB → {new_size:.0f}KB ({ratio:.0f}% 감소)")
5. 썸네일 생성
from PIL import Image
import os, globINPUT_DIR = "/path/to/images"
OUTPUT_DIR = "/path/to/thumbnails"
THUMB_SIZE = (300, 300) # 정사각형 썸네일
CROP = True # True: 정사각형 자르기, False: 비율 유지
os.makedirs(OUTPUT_DIR, exist_ok=True)
for path in glob.glob(os.path.join(INPUT_DIR, "*")):
ext = os.path.splitext(path)[1].lower()
if ext not in ('.jpg', '.jpeg', '.png', '.webp'):
continue
img = Image.open(path)
name = os.path.splitext(os.path.basename(path))[0]
if CROP:
# 중앙 크롭으로 정사각형
w, h = img.size
min_side = min(w, h)
left = (w - min_side) // 2
top = (h - min_side) // 2
right = (w + min_side) // 2
bottom = (h + min_side) // 2
img = img.crop((left, top, right, bottom))
img.thumbnail(THUMB_SIZE, Image.Resampling.LANCZOS)
out_path = os.path.join(OUTPUT_DIR, f"thumb_{name}.jpg")
img.save(out_path, 'JPEG', quality=85)
print(f"✅ 썸네일: {os.path.basename(path)} → {img.size}")
6. EXIF 메타데이터 제거
from PIL import Image
import os, globINPUT_DIR = "/path/to/images"
OUTPUT_DIR = "/path/to/cleaned"
os.makedirs(OUTPUT_DIR, exist_ok=True)
for path in glob.glob(os.path.join(INPUT_DIR, "*")):
ext = os.path.splitext(path)[1].lower()
if ext not in ('.jpg', '.jpeg', '.png', '.webp', '.tiff'):
continue
img = Image.open(path)
# EXIF 데이터 확인
exif = img.getexif() if hasattr(img, 'getexif') else {}
if exif:
print(f"📌 {os.path.basename(path)}: {len(exif)}개 EXIF 태그 제거됨")
# 데이터 없이 새로 저장 (EXIF 제거)
data = list(img.getdata())
img_no_exif = Image.new(img.mode, img.size)
img_no_exif.putdata(data)
out_path = os.path.join(OUTPUT_DIR, os.path.basename(path))
img_no_exif.save(out_path, quality=95)
print(f"완료! EXIF 제거된 이미지: {OUTPUT_DIR}")
7. 이미지 정보 요약
from PIL import Image
import os, globINPUT_DIR = "/path/to/images"
total_size = 0
results = []
for path in sorted(glob.glob(os.path.join(INPUT_DIR, "*"))):
ext = os.path.splitext(path)[1].lower()
if ext not in ('.jpg', '.jpeg', '.png', '.webp', '.gif', '.bmp'):
continue
img = Image.open(path)
size_kb = os.path.getsize(path) / 1024
total_size += size_kb
results.append({
'name': os.path.basename(path),
'size': f"{size_kb:.0f}KB",
'dimensions': f"{img.width}×{img.height}",
'mode': img.mode,
'format': img.format or 'Unknown',
})
print(f"📊 이미지 요약 ({len(results)}개, 총 {total_size/1024:.1f}MB)")
for r in results:
print(f" {r['name']}: {r['dimensions']} {r['mode']} {r['format']} ({r['size']})")
8. 종합 배치 처리 (한번에 여러 작업)
from PIL import Image
import os, glob설정
INPUT_DIR = "/path/to/images"
OUTPUT_DIR = "/path/to/output"
MAX_SIZE = 1200
FORMAT = "webp"
QUALITY = 85
ADD_WATERMARK = True
WATERMARK_TEXT = "© ICBM2"
STRIP_EXIF = Trueos.makedirs(OUTPUT_DIR, exist_ok=True)
for path in glob.glob(os.path.join(INPUT_DIR, "*")):
ext = os.path.splitext(path)[1].lower()
if ext not in ('.jpg', '.jpeg', '.png', '.webp', '.bmp', '.tiff'):
continue
img = Image.open(path)
name = os.path.splitext(os.path.basename(path))[0]
# 1. EXIF 기반 자동 회전
if hasattr(img, '_getexif') and img._getexif():
from PIL import ImageOps
img = ImageOps.exif_transpose(img)
# 2. 리사이즈
if img.width > MAX_SIZE or img.height > MAX_SIZE:
img.thumbnail((MAX_SIZE, MAX_SIZE), Image.Resampling.LANCZOS)
# 3. 워터마크
if ADD_WATERMARK:
# ... (위 워터마크 코드 참고)
# 4. EXIF 제거 후 저장
data = list(img.getdata())
clean = Image.new(img.mode, img.size)
clean.putdata(data)
out_path = os.path.join(OUTPUT_DIR, f"{name}.{FORMAT}")
clean.save(out_path, FORMAT, quality=QUALITY)
orig = os.path.getsize(path) / 1024
new = os.path.getsize(out_path) / 1024
print(f"✅ {os.path.basename(path)}: {orig:.0f}KB → {new:.0f}KB")
9. ImageMagick (터미널 명령어)
간단한 작업은 터미널이 더 빠를 수 있음:
# 리사이즈 (비율 유지)
mogrify -resize 800x800 -path /output/ /input/*.jpg포맷 변환
mogrify -format webp -quality 85 /input/*.jpg워터마크
composite -gravity southeast -dissolve 50% logo.png input.jpg output.jpgEXIF 제거
mogrify -strip /input/*.jpg썸네일 생성 (정사각형)
mogrify -thumbnail 300x300^ -gravity center -extent 300x300 -path /thumbs/ /input/*.jpg이미지 스프라이트 (여러 이미지를 하나로)
montage -geometry +2+2 -tile 4x0 /input/*.jpg sprite.jpg
주의사항
- WebP 변환 시 구형 브라우저 호환성 고려
- 워터마크는 PNG(투명도 지원)로 저장 권장
- 원본 파일은 백업 후 작업할 것
- GIF 애니메이션은 Pillow로 처리 시 첫 프레임만 저장됨 (ffmpeg 필요시 별도 처리)
Related Skills / 관련 스킬
architecture-diagram
Generate dark-themed SVG diagrams of software systems and cloud infrastructure as standalone HTML files with inline SVG graphics. Semantic component colors (cyan=frontend, emerald=backend, violet=database, amber=cloud/AWS, rose=security, orange=message bus), JetBrains Mono font, grid background. Best suited for software architecture, cloud/VPC topology, microservice maps, service-mesh diagrams, database + API layer diagrams, security groups, message buses — anything that fits a tech-infra deck with a dark aesthetic. If a more specialized diagramming skill exists for the subject (scientific, educational, hand-drawn, animated, etc.), prefer that — otherwise this skill can also serve as a general-purpose SVG diagram fallback. Based on Cocoon AI's architecture-diagram-generator (MIT).