fix(tkqc): iPhone HEIC 업로드 실패 → ImageMagick fallback 추가
증상: 사용자가 iPhone HEIC 사진을 관리함에서 업로드하면 400 Bad Request. 로그: ⚠️ pillow_heif 직접 처리 실패: Metadata not correctly assigned to image ❌ HEIF 처리도 실패: cannot identify image file 원인: pillow_heif 가 특정 iPhone 이 생성한 HEIC 의 메타데이터를 처리 못함. libheif 를 직접 사용하는 ImageMagick 이 더 범용적이라 system2-report/imageUploadService.js 와 동일한 패턴으로 fallback 추가. 변경: - Dockerfile: imagemagick + libheif1 apt-get 추가 + HEIC policy.xml 해제 - file_service.py: pillow_heif/PIL 실패 시 subprocess 로 magick/convert 호출해서 임시 파일로 JPEG 변환 후 다시 PIL 로 open Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,11 +2,16 @@ FROM python:3.11-slim
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# 시스템 패키지 설치 (gosu: entrypoint 에서 root→appuser 강등용)
|
# 시스템 패키지 설치
|
||||||
|
# - gosu: entrypoint 에서 root→appuser 강등용
|
||||||
|
# - imagemagick + libheif: HEIC(iPhone) 등 pillow_heif 가 처리 못하는 이미지 fallback (2026-04-09)
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
gcc \
|
gcc \
|
||||||
gosu \
|
gosu \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
imagemagick \
|
||||||
|
libheif1 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& sed -i 's|<policy domain="coder" rights="none" pattern="HEIC" />|<policy domain="coder" rights="read\|write" pattern="HEIC" />|' /etc/ImageMagick-6/policy.xml 2>/dev/null || true
|
||||||
|
|
||||||
# Python 의존성 설치
|
# Python 의존성 설치
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
|
|||||||
@@ -84,11 +84,48 @@ def save_base64_image(base64_string: str, prefix: str = "image") -> Optional[str
|
|||||||
print(f"✅ HEIF 재시도 성공: {image.format}, 모드: {image.mode}, 크기: {image.size}")
|
print(f"✅ HEIF 재시도 성공: {image.format}, 모드: {image.mode}, 크기: {image.size}")
|
||||||
except Exception as heif_e:
|
except Exception as heif_e:
|
||||||
print(f"❌ HEIF 처리도 실패: {heif_e}")
|
print(f"❌ HEIF 처리도 실패: {heif_e}")
|
||||||
print("❌ 지원되지 않는 이미지 형식")
|
image = None # ImageMagick fallback 시도
|
||||||
raise e
|
|
||||||
else:
|
else:
|
||||||
print("❌ HEIF 지원 라이브러리가 설치되지 않거나 처리 불가")
|
print("❌ HEIF 지원 라이브러리가 설치되지 않거나 처리 불가")
|
||||||
raise e
|
|
||||||
|
# ImageMagick fallback (pillow_heif 가 특정 iPhone HEIC 메타데이터를 처리 못하는 경우)
|
||||||
|
if image is None:
|
||||||
|
print("🔄 ImageMagick 으로 fallback 시도...")
|
||||||
|
try:
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.bin', delete=False, dir='/tmp') as tmp_in:
|
||||||
|
tmp_in.write(image_data)
|
||||||
|
tmp_in_path = tmp_in.name
|
||||||
|
tmp_out_path = tmp_in_path + '.jpg'
|
||||||
|
# convert 는 ImageMagick 6, magick 은 ImageMagick 7
|
||||||
|
cmd = None
|
||||||
|
for binary in ('magick', 'convert'):
|
||||||
|
try:
|
||||||
|
subprocess.run([binary, '-version'], capture_output=True, timeout=5, check=True)
|
||||||
|
cmd = binary
|
||||||
|
break
|
||||||
|
except (FileNotFoundError, subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
||||||
|
continue
|
||||||
|
if not cmd:
|
||||||
|
raise Exception("ImageMagick not installed")
|
||||||
|
result = subprocess.run(
|
||||||
|
[cmd, tmp_in_path, tmp_out_path],
|
||||||
|
capture_output=True, timeout=30, text=True
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception(f"ImageMagick 변환 실패: {result.stderr[:200]}")
|
||||||
|
image = Image.open(tmp_out_path)
|
||||||
|
image.load() # 파일 삭제 전 강제 로드
|
||||||
|
print(f"✅ ImageMagick fallback 성공: {image.format}, 모드: {image.mode}, 크기: {image.size}")
|
||||||
|
try:
|
||||||
|
os.unlink(tmp_in_path)
|
||||||
|
os.unlink(tmp_out_path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
except Exception as magick_e:
|
||||||
|
print(f"❌ ImageMagick fallback 실패: {magick_e}")
|
||||||
|
raise e # 원래 PIL 에러를 올림
|
||||||
|
|
||||||
# 이미지가 성공적으로 로드되지 않은 경우
|
# 이미지가 성공적으로 로드되지 않은 경우
|
||||||
if image is None:
|
if image is None:
|
||||||
|
|||||||
Reference in New Issue
Block a user