import subprocess
import sys
# Function to install required packages
def install_required_packages():
required_packages = [
'flask',
'google-generativeai',
'python-dotenv',
'Pillow',
'markdown2'
]
for package in required_packages:
try:
__import__(package.replace('-', '_'))
except ImportError:
print(f"Installing {package}...")
subprocess.check_call([sys.executable, "-m", "pip", "install", package])
# Install required packages
install_required_packages()
from flask import Flask, render_template_string, request, jsonify, Response
from werkzeug.utils import secure_filename
import os
import google.generativeai as genai
import base64
from dotenv import load_dotenv
from PIL import Image
import io
import json
import markdown2
app = Flask(__name__)
# Configuration
load_dotenv()
GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')
genai.configure(api_key=GOOGLE_API_KEY)
model = genai.GenerativeModel('gemini-1.5-pro-002')
UPLOAD_FOLDER = 'uploads'
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
# Updated System prompt for accessibility-focused image analysis
SYSTEM_PROMPT = """
You are an experienced Accessibility Document Specialist who converts visual content into accessible formats for visually impaired individuals.
Your task is to analyze the provided product description image and convert ALL content into an accessible format, following these strict requirements:
1. Output Format:
- Provide the COMPLETE content in Korean
- Do NOT summarize or paraphrase any content
- Include ALL text exactly as written in the image
- Convert ONLY the product description section
2. Text Requirements:
- Extract and present ALL text content exactly as shown
- Maintain the original structure and order
- Do not omit any text, no matter how minor it may seem
- Keep all product specifications, features, and details intact
3. Image Description Requirements:
- Preface each image description with '사진 설명:'
- Provide detailed descriptions of all images
- Describe every visual element present
- Do not summarize or simplify image descriptions
4. Strict Guidelines:
- No content summarization
- No interpretation or paraphrasing
- No additional commentary or explanations
- Focus only on the product description area
- Maintain the exact order of information as shown in the original
Please analyze the image and provide a complete, detailed transcription that includes every piece of text and image description from the product description section.
"""
# HTML template as a string
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>이미지 분석기</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet">
<style>
/* 마크다운 스타일 유지 */
.markdown-content h1 { font-size: 2em; font-weight: bold; margin: 0.67em 0; }
.markdown-content h2 { font-size: 1.5em; font-weight: bold; margin: 0.83em 0; }
.markdown-content h3 { font-size: 1.17em; font-weight: bold; margin: 1em 0; }
.markdown-content ul { list-style-type: disc; padding-left: 2em; margin: 1em 0; }
.markdown-content ol { list-style-type: decimal; padding-left: 2em; margin: 1em 0; }
.markdown-content code { background-color: #f0f0f0; padding: 0.2em 0.4em; border-radius: 3px; }
.markdown-content pre { background-color: #f0f0f0; padding: 1em; border-radius: 3px; overflow-x: auto; }
.markdown-content blockquote { border-left: 4px solid #ddd; padding-left: 1em; margin: 1em 0; }
.markdown-content table { border-collapse: collapse; margin: 1em 0; }
.markdown-content th, .markdown-content td { border: 1px solid #ddd; padding: 0.5em; }
</style>
</head>
<body class="bg-gray-100 min-h-screen p-8">
<div class="max-w-2xl mx-auto">
<h1 class="text-3xl font-bold text-center mb-8">이미지 분석기</h1>
<form id="uploadForm" class="mb-8">
<div class="flex items-center justify-center w-full">
<label class="flex flex-col w-full h-32 border-4 border-dashed hover:bg-gray-100 hover:border-gray-300">
<div class="flex flex-col items-center justify-center pt-7">
<i class="fas fa-cloud-upload-alt fa-3x text-gray-400 group-hover:text-gray-600"></i>
<p class="pt-1 text-sm tracking-wider text-gray-400 group-hover:text-gray-600">
이미지를 선택하세요
</p>
</div>
<input type="file" name="image" class="opacity-0" accept="image/*" required />
</label>
</div>
<button type="submit" class="mt-4 w-full bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
분석하기
</button>
</form>
<div id="loading" class="hidden text-center mb-4">
<i class="fas fa-spinner fa-spin fa-2x text-blue-500"></i>
<p class="mt-2 text-gray-600">이미지 분석 중...</p>
</div>
<div id="response" class="hidden p-4 rounded-lg"></div>
</div>
<script>
$(document).ready(function() {
$('#uploadForm').on('submit', function(e) {
e.preventDefault();
$('#loading').removeClass('hidden');
$('#response')
.removeClass('hidden')
.removeClass('bg-red-100 text-red-700')
.addClass('bg-green-100 text-gray-700')
.empty();
let formData = new FormData(this);
$.ajax({
url: '/analyze',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
$('#loading').addClass('hidden');
if (response.error) {
$('#response')
.removeClass('bg-green-100')
.addClass('bg-red-100 text-red-700')
.html('<i class="fas fa-exclamation-circle mr-2"></i>' + response.error);
} else {
$('#response').html('<div class="markdown-content whitespace-pre-wrap">' + response.result + '</div>');
}
},
error: function() {
$('#loading').addClass('hidden');
$('#response')
.removeClass('bg-green-100')
.addClass('bg-red-100 text-red-700')
.html('<i class="fas fa-exclamation-circle mr-2"></i>이미지 분석 중 오류가 발생했습니다.');
}
});
});
});
</script>
</body>
</html>
"""
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def analyze_image_with_gemini(image_path):
try:
img = Image.open(image_path)
img_byte_arr = io.BytesIO()
img.save(img_byte_arr, format=img.format)
img_byte_arr = img_byte_arr.getvalue()
image_parts = [
{
"mime_type": f"image/{img.format.lower()}",
"data": base64.b64encode(img_byte_arr).decode('utf-8')
}
]
# 스트리밍을 False로 설정하고 한 번에 응답 받기
response = model.generate_content(
[SYSTEM_PROMPT, image_parts[0]],
stream=False, # 스트리밍 비활성화
generation_config={
"temperature": 0.4,
"top_p": 0.8,
"top_k": 40
}
)
# 마크다운을 HTML로 변환하여 반환
if response.text:
html_content = markdown2.markdown(
response.text,
extras=['tables', 'fenced-code-blocks']
)
return html_content
except Exception as e:
return f"Error analyzing image: {str(e)}"
@app.route('/')
def index():
return render_template_string(HTML_TEMPLATE)
@app.route('/analyze', methods=['POST'])
def analyze_image():
if 'image' not in request.files:
return jsonify({'error': 'No file part'})
file = request.files['image']
if file.filename == '':
return jsonify({'error': 'No selected file'})
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
try:
result = analyze_image_with_gemini(file_path)
return jsonify({'result': result})
finally:
os.remove(file_path)
return jsonify({'error': 'File type not allowed'})
if __name__ == '__main__':
if not os.path.exists(app.config['UPLOAD_FOLDER']):
os.makedirs(app.config['UPLOAD_FOLDER'])
app.run(debug=True, port=8000)