diff --git a/backend/app.py b/backend/app.py index 9e30191..3b26ab8 100644 --- a/backend/app.py +++ b/backend/app.py @@ -5782,6 +5782,115 @@ def api_delete_product_image(barcode): return jsonify({'success': False, 'error': str(e)}), 500 +@app.route('/api/admin/product-images//replace', methods=['POST']) +def api_replace_product_image(barcode): + """이미지 URL로 교체""" + import sqlite3 + import requests + import base64 + from PIL import Image + from io import BytesIO + + try: + data = request.get_json() + image_url = data.get('image_url', '').strip() + + if not image_url: + return jsonify({'success': False, 'error': 'URL 필요'}), 400 + + # 다양한 User-Agent와 헤더로 시도 + headers_list = [ + { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8', + 'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7', + 'Referer': 'https://www.google.com/', + }, + { + 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1', + 'Accept': 'image/*,*/*;q=0.8', + }, + { + 'User-Agent': 'Googlebot-Image/1.0', + } + ] + + response = None + for headers in headers_list: + try: + response = requests.get(image_url, headers=headers, timeout=15, allow_redirects=True) + if response.status_code == 200 and len(response.content) > 1000: + break + except: + continue + + if not response or response.status_code != 200: + return jsonify({'success': False, 'error': f'이미지 다운로드 실패 (상태: {response.status_code if response else "연결실패"})'}), 400 + + # PIL로 이미지 처리 + try: + img = Image.open(BytesIO(response.content)) + + # RGBA -> RGB 변환 + if img.mode == 'RGBA': + bg = Image.new('RGB', img.size, (255, 255, 255)) + bg.paste(img, mask=img.split()[3]) + img = bg + elif img.mode != 'RGB': + img = img.convert('RGB') + + # 리사이즈 (최대 500px) + max_size = 500 + if max(img.size) > max_size: + ratio = max_size / max(img.size) + new_size = tuple(int(dim * ratio) for dim in img.size) + img = img.resize(new_size, Image.LANCZOS) + + # base64 변환 + buffer = BytesIO() + img.save(buffer, format='JPEG', quality=85) + image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8') + + # 썸네일 생성 + thumb_size = 100 + ratio = thumb_size / max(img.size) + thumb_img = img.resize(tuple(int(dim * ratio) for dim in img.size), Image.LANCZOS) + thumb_buffer = BytesIO() + thumb_img.save(thumb_buffer, format='JPEG', quality=80) + thumbnail_base64 = base64.b64encode(thumb_buffer.getvalue()).decode('utf-8') + + except Exception as e: + return jsonify({'success': False, 'error': f'이미지 처리 실패: {str(e)}'}), 400 + + # SQLite 업데이트 + db_path = os.path.join(os.path.dirname(__file__), 'db', 'product_images.db') + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + cursor.execute(""" + UPDATE product_images + SET image_base64 = ?, thumbnail_base64 = ?, image_url = ?, + status = 'manual', error_message = NULL, updated_at = datetime('now') + WHERE barcode = ? + """, (image_base64, thumbnail_base64, image_url, barcode)) + + if cursor.rowcount == 0: + # 레코드가 없으면 새로 생성 + cursor.execute(""" + INSERT INTO product_images (barcode, image_base64, thumbnail_base64, image_url, status, product_name) + VALUES (?, ?, ?, ?, 'manual', ?) + """, (barcode, image_base64, thumbnail_base64, image_url, barcode)) + + conn.commit() + conn.close() + + return jsonify({'success': True, 'message': '이미지 교체 완료'}) + + except Exception as e: + logging.error(f"이미지 교체 오류: {e}") + return jsonify({'success': False, 'error': str(e)}), 500 + + @app.route('/api/admin/product-images/stats') def api_product_images_stats(): """이미지 통계""" diff --git a/backend/templates/admin_product_images.html b/backend/templates/admin_product_images.html index 0a9cd35..efac690 100644 --- a/backend/templates/admin_product_images.html +++ b/backend/templates/admin_product_images.html @@ -410,6 +410,32 @@ + + +