android, iOS 모바일 앱 구현 시 BE API를 구현해야할 일이 생깁니다.
그런데 iOS의 경우에는 장비들이 어느 정도 정해져 있지만 android의 경우에는 많이 파편화 되어 있습니다.
그래서 파일 첨부 시 다양한 사이즈 형태의 파일을 접하게 됩니다.
모든 기기에서 앨범, 카메라 사용 시 처리하게 대응할 수 있지만 모든 장비를 대응할 수는 없기 때문에
BE 서버에서 처리하는게 더 효율적일 수 있습니다.
flask로 파일을 처리하는 API 코드 입니다.
import io
from flask import Flask, request, redirect, url_for, render_template
from werkzeug.utils import secure_filename
from PIL import Image, ExifTags
import os
import pyheif
# 프로젝트 설명
# 안드로이드, iOS등 의 경우 파일 업로드 시 다양한 이미지 포맷이 존재하고 파일 사이즈가 다양하기 때문에
# 서버에서 포맷 및 파일 용량을 조정해서 처리하는 로직 입니다.
# / 라우트에서 테스트가 가능하고 smaple_files 폴더에 테스트 파일이 있습니다.
app = Flask(__name__)
# 업로드된 파일을 저장할 디렉토리 설정
UPLOAD_FOLDER = './uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
# 업로드 가능한 확장자 목록 설정 (예: 이미지 파일)
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'heic', 'heif', 'webp'}
# 파일 확장자 확인 함수
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
# 파일 업로드 폼 라우트
@app.route('/')
def upload_form():
return '''
<!doctype html>
<title>Upload a File</title>
<h1>Upload a File</h1>
<form method="post" enctype="multipart/form-data" action="/upload">
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
'''
# 파일 업로드 처리 라우트
@app.route('/upload', methods=['POST'])
def upload_file():
# 파일이 제대로 제출됐는지 확인
if 'file' not in request.files:
return 'No file part'
file = request.files['file']
# 파일 이름이 비어 있는지 확인
if file.filename == '':
return 'No selected file'
# 파일이 업로드 가능하다면 처리
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
# 이미지를 처리하여 변환 및 크기 조정
processed_file, new_filename = process_image(file)
new_filename = secure_filename(new_filename) # 파일명 안전하게 처리
# 변환된 파일 저장
save_path = os.path.join(app.config['UPLOAD_FOLDER'], new_filename)
with open(save_path, 'wb') as f:
f.write(processed_file.read())
return f'File successfully processed and uploaded: {new_filename}'
return 'File type not allowed'
# 해당 이미지 파일의 화면 방향을 구하는 함수 (EXIF Orientation)
# 방향을 고려하지 않을 경우 후처리 시 이미지가 회전되어 버림
def get_orientation(image):
# EXIF 정보 가져오기 (getexif() 사용)
exif_data = image.getexif()
orientation = None
if exif_data is not None:
# EXIF 태그를 읽어서 Orientation 값 찾기
for tag, value in exif_data.items():
tag_name = ExifTags.TAGS.get(tag, tag)
if tag_name == "Orientation":
orientation = value
break
return orientation
# 파일 사이즈가 1MB가 이상인 경우 리사이즈 함
# 리사이즈 시 jpg로 포멧을 변경
# 업로드 된 파일이 1MB 이하인 경우 ['.jpg', '.jpeg', '.png', '.gif'] 확장자인 경우는 그래도 유지하고 그 외의 확장자는 jpg로 변경한다.
def process_image(uploaded_file):
# 파일 포인터를 처음으로 이동
uploaded_file.seek(0)
content = uploaded_file.read()
filename = uploaded_file.filename
ext = os.path.splitext(filename)[1].lower()
supported_formats = ['.jpg', '.jpeg', '.png', '.gif']
if ext in ['.heic', '.heif']:
try:
heif_file = pyheif.read(content)
image = Image.frombytes(
heif_file.mode,
heif_file.size,
heif_file.data,
"raw",
heif_file.mode,
heif_file.stride,
)
filename = os.path.splitext(filename)[0] + '.jpg'
ext = '.jpg'
except Exception as e:
print(f"Error processing HEIC file: {e}")
raise
elif ext in supported_formats:
try:
image = Image.open(io.BytesIO(content))
except Exception as e:
print(f"Error processing supported format: {e}")
raise
else:
try:
image = Image.open(io.BytesIO(content))
filename = os.path.splitext(filename)[0] + '.jpg'
ext = '.jpg'
except Exception as e:
print(f"Error processing unsupported format: {e}")
raise
# Orientation 정보 가져오기
orientation = get_orientation(image)
# Orientation 값을 기반으로 이미지 회전 적용
if orientation:
if orientation == 3:
image = image.rotate(180, expand=True)
elif orientation == 6:
image = image.rotate(270, expand=True)
elif orientation == 8:
image = image.rotate(90, expand=True)
output_io = io.BytesIO()
quality = 85
try:
image.save(output_io, format='JPEG', quality=quality)
except Exception as e:
print(f"Error saving image: {e}")
output_io.seek(0)
output_io.truncate()
image.save(output_io, format='JPEG', quality=quality)
file_size = output_io.tell()
while file_size > 1 * 1024 * 1024 and quality > 10:
quality -= 5
output_io.seek(0)
output_io.truncate()
image.save(output_io, format='JPEG', quality=quality)
file_size = output_io.tell()
if file_size > 1 * 1024 * 1024:
width, height = image.size
scale_factor = (1 * 1024 * 1024 / file_size) ** 0.5
new_width = int(width * scale_factor)
new_height = int(height * scale_factor)
image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
output_io.seek(0)
output_io.truncate()
image.save(output_io, format='JPEG', quality=quality)
output_io.seek(0)
return output_io, filename
if __name__ == '__main__':
# 업로드 폴더가 없다면 생성
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
app.run(host='0.0.0.0', port=4999)
해당 코드 아래의 조건에 대응하게 되어 있습니다.
- 대용량 파일의 경우 1024 px로 리사이징 합니다.
- exif를 이용해서 방향을 보정합니다.
- iOS의 경우 HEIC, HEIF의 경우 jpeg로 변환합니다.
'👩💻 > 개발' 카테고리의 다른 글
FE 기능 - CSS 컨테이너 쿼리(Container Queries)와 스타일 쿼리(Style Queries) (0) | 2025.01.09 |
---|---|
FE 기능 - CSS 앵커 포지셔닝 API 사용하기 (0) | 2025.01.09 |
macos(맥북) docker에 MSSQL 설치하기 (0) | 2025.01.09 |
macos(맥북) docker에 OpenLDAP 설치하기 (0) | 2025.01.09 |
macos(맥북) Oracle docker에 설치하는 방법 (0) | 2025.01.09 |