-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
263 lines (215 loc) · 10.1 KB
/
Copy pathapp.py
File metadata and controls
263 lines (215 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
#11/13 이미지 jpg 설정 및 모든 이미지가 300kb 넘지는 않되, 가깝게 조정
from flask import Flask, request, jsonify, send_from_directory
from PIL import Image, ImageDraw, ImageFont
import openai
import base64 #react 이미지때문에 추가 11/17
import os
import requests
from io import BytesIO
import concurrent.futures
from PIL import UnidentifiedImageError #이미지 저장 문제 11/17
app = Flask(__name__)
from flask_cors import CORS
CORS(app) # CORS 설정을 통해 외부 도메인에서 API에 접근 가능하도록 허용
# OpenAI API 키 설정
openai.api_key = ''
# 정적 파일, HTML 파일, 폰트 경로 설정
STATIC_FOLDER = os.path.join(os.getcwd(), 'static')
HTML_FOLDER = os.path.join(STATIC_FOLDER, 'html')
REACT_FOLDER = os.path.join(STATIC_FOLDER, 'react') # React 빌드 파일 경로 11/17
FONTS_FOLDER = os.path.join(os.getcwd(), 'fonts')
FONT_PATH = os.path.join(FONTS_FOLDER, 'NanumBrush.ttf')
# 정적 폴더가 없는 경우 생성
if not os.path.exists(STATIC_FOLDER):
os.makedirs(STATIC_FOLDER)
if not os.path.exists(REACT_FOLDER): # React 폴더가 없는 경우 경고 출력 11/17
print("⚠️ React build 폴더가 없습니다. React 빌드 파일을 static/react에 배치하세요.")
# # 메시지를 짧게 요약하는 함수
# def generate_short_message(message):
# response = openai.ChatCompletion.create(
# model="gpt-4-turbo",
# #프롬프트 수정 11/20 종종 영어로 출력되어 in korean 추가
# messages=[{"role": "user", "content": f"{message}. within 20 letters in korean"}]
# )
# return response.choices[0].message['content'].strip()
def generate_short_message(message):
response = openai.ChatCompletion.create(
model="gpt-4-turbo",
# 프롬프트 수정 11/20 종종 영어로 출력되어 in korean 추가
messages=[{"role": "user", "content": f"{message}. within 20 letters in korean"}]
)
# 결과에서 큰따옴표 제거
result = response.choices[0].message['content'].strip()
return result.replace('"', '') # 큰따옴표를 제거 11/20 수정
# 텍스트 위치를 계산하는 함수
def calculate_text_position(image, position_hint, text, font):
draw = ImageDraw.Draw(image)
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
# 위치에 따라 x, y 좌표 계산
if position_hint == 'top left':
x, y = 10, 10
elif position_hint == 'top right':
x, y = image.width - text_width - 10, 10
elif position_hint == 'bottom right':
x, y = image.width - text_width - 10, image.height - text_height - 10
elif position_hint == 'bottom left':
x, y = 10, image.height - text_height - 10
else:
x = (image.width - text_width) / 2
y = (image.height - text_height) / 2
# 텍스트가 이미지 밖으로 나가지 않도록 조정
return max(0, min(x, image.width - text_width)), max(0, min(y, image.height - text_height))
# 텍스트를 주어진 너비에 맞게 줄바꿈하는 함수
def wrap_text(text, font, max_width):
lines = []
words = text.split(' ')
line = []
for word in words:
test_line = ' '.join(line + [word])
width, _ = font.getbbox(test_line)[2:]
# 텍스트의 너비가 최대 너비를 초과할 경우 줄바꿈
if width <= max_width:
line.append(word)
else:
lines.append(' '.join(line))
line = [word]
if line:
lines.append(' '.join(line))
return '\n'.join(lines)
# 이미지 파일 크기를 조정하는 함수
# 이미지 압축 및 저장 함수
def save_image_with_compression(image, path, max_size):
quality = 95 # 초기 JPEG 품질 설정
while quality > 10:
with BytesIO() as buf:
image.save(buf, 'JPEG', quality=quality)
size = buf.tell()
if size <= max_size:
with open(path, 'wb') as f:
f.write(buf.getvalue())
print(f"Saved '{path}' with size {size / 1024:.2f} KB (Quality: {quality})")
break
quality -= 5
@app.route('/generate', methods=['POST'])
def generate_image():
try:
data = request.json
title = data.get('title', '제목 없음')
message = data.get('message', '내용 없음')
instruction = data.get('instruction', '')
font_name = data.get('font', 'NanumBrush.ttf')
text_color = data.get('textColor', 'black')
border_color = data.get('borderColor', 'white')
position = data.get('position', 'center')
font_size = data.get('fontSize', 50)
painting_style = data.get('painting_style', '선택 안함')
# 메시지 요약 생성
summarized_message = generate_short_message(message)
print("summarized_message: " + summarized_message + "\n")
result_message = summarized_message
print("result_message: " + result_message + "\n")
# 폰트 파일 경로 설정
font_path = os.path.join(FONTS_FOLDER, font_name)
# # 메시지 요약 생성 11/20 수정 -> 이중으로 요약되어 영어로 출력되는 문제있음
# summarized_message = generate_short_message(message)
# DALL·E에 이미지 생성을 요청하는 프롬프트 생성
prompt = (
f"Create an artistic image in the style of {painting_style}. "
f"The theme is: {title}. "
# f"Exclude all text, letters, and symbols. Follow these additional instructions: {instruction}"
f"Follow these additional instructions: {instruction}"
)
# DALL·E API를 통해 이미지 생성
dalle_response = openai.Image.create(
model="dall-e-3",
prompt=prompt,
n=1,
size="1024x1024"
)
# DALL·E가 반환한 이미지 URL로부터 이미지 다운로드
image_url = dalle_response['data'][0]['url']
image_response = requests.get(image_url)
img = Image.open(BytesIO(image_response.content))
# 기본 생성 이미지를 저장 (텍스트가 없는 원본 이미지)
original_img_path = os.path.join(STATIC_FOLDER, 'original.jpg')
save_image_with_compression(img, original_img_path, 300 * 1024) # 300KB 제한 적용
# 폰트를 불러옴 (기본 폰트로 대체 가능)
try:
font = ImageFont.truetype(font_path, font_size)
except IOError:
font = ImageFont.load_default()
# 텍스트 줄바꿈 처리
wrapped_message = wrap_text(summarized_message, font, img.width - 20)
# 텍스트 위치 계산
x, y = calculate_text_position(img, position, wrapped_message, font)
draw = ImageDraw.Draw(img)
# 텍스트 테두리 그리기
for offset in [-1, 1]:
draw.text((x + offset, y), wrapped_message, font=font, fill=border_color)
draw.text((x, y + offset), wrapped_message, font=font, fill=border_color)
# 텍스트 그리기
draw.text((x, y), wrapped_message, font=font, fill=text_color)
# 텍스트가 추가된 이미지를 로컬에 저장 (최종 이미지)
result_img_path = os.path.join(STATIC_FOLDER, 'result.jpg')
save_image_with_compression(img, result_img_path, 300 * 1024) # 300KB 제한 적용
# 화면에는 result.jpg의 URL만 반환
return jsonify({'imageUrl': f'http://localhost:5000/static/result.jpg'}), 200
except Exception as e:
return jsonify({'error': str(e)}), 500
# 이미지 폴더 내 저장 (실행 안됨. 추후 수정 필요)
@app.route('/upload', methods=['POST'])
def upload_image():
try:
data = request.json
if not data:
print("No data received!")
return jsonify({"error": "No data received"}), 400
image_data = data.get('image')
if not image_data:
print("No image data in request!")
return jsonify({"error": "No image data"}), 400
print("Received Image Data:", image_data[:50]) # 데이터 URL 일부 출력
# 데이터 URL에서 이미지 데이터 추출
header, encoded = image_data.split(",", 1)
image_bytes = base64.b64decode(encoded)
print("Decoded Image Bytes Length:", len(image_bytes)) # 디코딩된 바이트 길이 출력
# 이미지 열기 및 복사
img_buf = BytesIO(image_bytes)
img = Image.open(img_buf).convert("RGB") # 복사 및 RGB 변환
img_buf.close() # BytesIO 객체를 명시적으로 닫기
img.load() # 이미지 데이터를 메모리에 로드하여 BytesIO와의 의존성 제거
print("Image format:", img.format)
# 이미지 저장 경로
image_path = os.path.join(STATIC_FOLDER, 'image_edit.jpg')
print("Image will be saved to:", image_path)
# 이미지 압축 및 저장
save_image_with_compression(img, image_path, 300 * 1024)
print("Image saved successfully!")
return jsonify({"message": "Image saved successfully", "imagePath": "/static/image_edit.jpg"}), 200
except Exception as e:
print("Error occurred:", str(e)) # 에러 메시지 출력
return jsonify({"error": str(e)}), 500
# React 정적 파일 제공 라우트 추가 11/17
@app.route('/react')
def serve_react():
return send_from_directory(os.path.join(STATIC_FOLDER, 'react'), 'index.html')
@app.route('/react/<path:filename>')
def serve_react_static(filename):
return send_from_directory(os.path.join(STATIC_FOLDER, 'react'), filename)
# 정적 파일 제공
@app.route('/static/<path:filename>')
def serve_static(filename):
return send_from_directory(STATIC_FOLDER, filename)
# HTML 파일 제공
@app.route('/')
def serve_index():
return send_from_directory(HTML_FOLDER, 'index.html')
# 폰트 파일 제공
@app.route('/fonts/<path:filename>')
def serve_fonts(filename):
return send_from_directory('fonts', filename)
# Flask 앱 실행
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)