Files
C.E.L_Slide_test2/src/image_utils.py

159 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""이미지 크기 측정 + HTML 이미지 삽입 유틸리티.
MDX 콘텐츠에서 이미지 참조를 추출하고, 로컬 파일의 크기를 Pillow로 측정한다.
다운로드 HTML에서 이미지가 보이도록 base64 data URI로 변환한다.
"""
from __future__ import annotations
import base64
import logging
import re
from pathlib import Path
from typing import Any
logger = logging.getLogger(__name__)
# 웹 표준 이미지 포맷
IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"}
MIME_MAP = {
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".webp": "image/webp",
".svg": "image/svg+xml",
}
def get_image_sizes(content: str, base_path: str) -> list[dict[str, Any]]:
"""콘텐츠에서 이미지 참조를 추출하고 로컬 파일 크기를 측정한다.
Args:
content: MDX/텍스트 콘텐츠
base_path: 이미지 파일 기준 폴더 경로
Returns:
[{"path": "/assets/images/DX1.png", "width": 800, "height": 600,
"ratio": 1.33, "orientation": "landscape"}]
"""
if not base_path:
return []
base = Path(base_path)
images: list[dict[str, Any]] = []
for match in re.finditer(r"!\[.*?\]\((.*?)\)", content):
rel_path = match.group(1).strip()
abs_path = base / rel_path.lstrip("/")
if abs_path.suffix.lower() not in IMAGE_EXTENSIONS:
continue
# 경로가 이중으로 붙는 경우 방어 (base가 이미 하위 폴더를 포함할 때)
if not abs_path.exists():
# rel_path에서 파일명만 추출하여 base 하위에서 재검색
filename = Path(rel_path).name
found = list(base.rglob(filename))
if found:
abs_path = found[0]
logger.info(f"이미지 경로 재탐색 성공: {filename}{abs_path}")
# samples/images/, samples/mdx_batch/ 에서도 탐색
if not abs_path.exists():
filename = Path(rel_path).name
for search_dir in [Path("samples/images"), Path("samples/mdx_batch")]:
if search_dir.exists():
found = list(search_dir.rglob(filename))
if found:
abs_path = found[0]
logger.info(f"이미지 경로 확장 탐색 성공: {filename}{abs_path}")
break
if not abs_path.exists():
logger.warning(f"이미지 파일 미발견: {abs_path}")
images.append({
"path": rel_path,
"width": 0,
"height": 0,
"ratio": 0,
"orientation": "not_found",
})
continue
try:
from PIL import Image
with Image.open(abs_path) as img:
w, h = img.size
ratio = round(w / h, 2) if h > 0 else 1.0
if ratio > 1.2:
orientation = "landscape"
elif ratio < 0.8:
orientation = "portrait"
else:
orientation = "square"
images.append({
"path": rel_path,
"width": w,
"height": h,
"ratio": ratio,
"orientation": orientation,
})
logger.info(f"이미지 크기: {rel_path}{w}×{h}px ({orientation})")
except Exception as e:
logger.warning(f"이미지 크기 측정 실패 ({rel_path}): {e}")
images.append({
"path": rel_path,
"width": 0,
"height": 0,
"ratio": 0,
"orientation": "error",
})
return images
def embed_images(html: str, base_path: str) -> str:
"""HTML의 이미지 src를 base64 data URI로 변환한다.
다운로드된 HTML 파일에서 로컬 이미지가 보이도록
상대 경로를 base64 인라인으로 교체한다.
Args:
html: 렌더링된 HTML
base_path: 이미지 파일 기준 폴더 경로
Returns:
이미지가 base64로 삽입된 HTML
"""
if not base_path:
return html
base = Path(base_path)
def replace_src(match: re.Match) -> str:
src = match.group(1)
abs_path = base / src.lstrip("/")
if not abs_path.exists():
return match.group(0)
suffix = abs_path.suffix.lower()
mime = MIME_MAP.get(suffix, "application/octet-stream")
try:
data = base64.b64encode(abs_path.read_bytes()).decode()
return f'src="data:{mime};base64,{data}"'
except Exception:
return match.group(0)
return re.sub(
r'src="(/[^"]+\.(?:png|jpg|jpeg|gif|webp|svg))"',
replace_src,
html,
)