This commit is contained in:
Timothy Jaeryang Baek committed 2026-06-29 10:51:52 -05:00
1 parent 8c7428122b
commit 39837e0a3a
2 files changed
+80 -16

No files matched your search

+2
View File
@@ -1426,6 +1426,8 @@ IMAGE_EDIT_MODEL = os.getenv('IMAGE_EDIT_MODEL', '')
IMAGE_EDIT_SIZE = os.getenv('IMAGE_EDIT_SIZE', '') IMAGE_EDIT_SIZE = os.getenv('IMAGE_EDIT_SIZE', '')
ENABLE_OPENAI_IMAGE_EDIT_NORMALIZATION = os.getenv('ENABLE_OPENAI_IMAGE_EDIT_NORMALIZATION', 'true').lower() == 'true'
IMAGES_EDIT_OPENAI_API_BASE_URL = os.getenv('IMAGES_EDIT_OPENAI_API_BASE_URL', OPENAI_API_BASE_URL) IMAGES_EDIT_OPENAI_API_BASE_URL = os.getenv('IMAGES_EDIT_OPENAI_API_BASE_URL', OPENAI_API_BASE_URL)
IMAGES_EDIT_OPENAI_API_VERSION = os.getenv('IMAGES_EDIT_OPENAI_API_VERSION', '') IMAGES_EDIT_OPENAI_API_VERSION = os.getenv('IMAGES_EDIT_OPENAI_API_VERSION', '')
+78 -16
View File
@@ -16,8 +16,10 @@ from urllib.parse import quote, urlparse
import aiohttp import aiohttp
from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
from PIL import Image, ImageOps
from open_webui.config import ( from open_webui.config import (
CACHE_DIR, CACHE_DIR,
ENABLE_OPENAI_IMAGE_EDIT_NORMALIZATION,
IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN, IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN,
IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN, IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN,
) )
@@ -53,6 +55,14 @@ IMAGE_CACHE_DIR.mkdir(parents=True, exist_ok=True)
router = APIRouter() router = APIRouter()
IMAGE_FILE_EXTENSIONS = {
'image/jpeg': '.jpg',
'image/jpg': '.jpg',
'image/mpo': '.jpg',
'image/png': '.png',
'image/webp': '.webp',
}
IMAGE_CONFIG_KEYS = { IMAGE_CONFIG_KEYS = {
'ENABLE_IMAGE_GENERATION': 'image_generation.enable', 'ENABLE_IMAGE_GENERATION': 'image_generation.enable',
'ENABLE_IMAGE_PROMPT_GENERATION': 'image_generation.prompt.enable', 'ENABLE_IMAGE_PROMPT_GENERATION': 'image_generation.prompt.enable',
@@ -104,6 +114,61 @@ def config_updates(data: dict, key_map: dict[str, str]) -> dict:
return {key_map[field]: value for field, value in data.items() if field in key_map} return {key_map[field]: value for field, value in data.items() if field in key_map}
def normalize_openai_edit_image_data_url(data_url: str) -> str:
if not data_url.startswith('data:') or ',' not in data_url:
return data_url
header, encoded = data_url.split(',', 1)
mime_type = header.split(';')[0].lstrip('data:').lower()
if mime_type not in {'image/jpeg', 'image/jpg', 'image/mpo'}:
return data_url
try:
image_bytes = base64.b64decode(encoded)
with Image.open(io.BytesIO(image_bytes)) as image:
orientation = image.getexif().get(274)
needs_normalization = (
mime_type == 'image/mpo'
or image.format == 'MPO'
or getattr(image, 'n_frames', 1) > 1
or orientation not in (None, 1)
or image.mode not in ('RGB', 'L')
)
if not needs_normalization:
return data_url
image.seek(0)
image = ImageOps.exif_transpose(image)
if image.mode != 'RGB':
image = image.convert('RGB')
output = io.BytesIO()
image.save(output, format='JPEG', quality=95)
normalized_image = base64.b64encode(output.getvalue()).decode('utf-8')
return f'data:image/jpeg;base64,{normalized_image}'
except Exception as e:
log.debug(f'Image edit normalization skipped: {e}')
return data_url
def get_image_file_item(base64_string, param_name='image', normalize=False):
data = normalize_openai_edit_image_data_url(base64_string) if normalize else base64_string
header, encoded = data.split(',', 1)
mime_type = header.split(';')[0].lstrip('data:') or 'image/png'
image_data = base64.b64decode(encoded)
extension = IMAGE_FILE_EXTENSIONS.get(mime_type.lower()) or mimetypes.guess_extension(mime_type) or '.png'
return (
param_name,
(
f'{uuid.uuid4()}{extension}',
io.BytesIO(image_data),
mime_type,
),
)
async def set_image_model(request: Request, model: str): async def set_image_model(request: Request, model: str):
log.info(f'Setting image model to {model}') log.info(f'Setting image model to {model}')
await Config.upsert({'image_generation.model': model}) await Config.upsert({'image_generation.model': model})
@@ -915,20 +980,6 @@ async def image_edits(
detail=ERROR_MESSAGES.DEFAULT(e, 'Error loading image'), detail=ERROR_MESSAGES.DEFAULT(e, 'Error loading image'),
) )
def get_image_file_item(base64_string, param_name='image'):
data = base64_string
header, encoded = data.split(',', 1)
mime_type = header.split(';')[0].lstrip('data:')
image_data = base64.b64decode(encoded)
return (
param_name,
(
f'{uuid.uuid4()}.png',
io.BytesIO(image_data),
mime_type if mime_type else 'image/png',
),
)
try: try:
if image_config.IMAGE_EDIT_ENGINE == 'openai': if image_config.IMAGE_EDIT_ENGINE == 'openai':
headers = { headers = {
@@ -956,10 +1007,21 @@ async def image_edits(
files = [] files = []
if isinstance(form_data.image, str): if isinstance(form_data.image, str):
files = [get_image_file_item(form_data.image)] files = [
get_image_file_item(
form_data.image,
normalize=ENABLE_OPENAI_IMAGE_EDIT_NORMALIZATION,
)
]
elif isinstance(form_data.image, list): elif isinstance(form_data.image, list):
for img in form_data.image: for img in form_data.image:
files.append(get_image_file_item(img, 'image[]')) files.append(
get_image_file_item(
img,
'image[]',
normalize=ENABLE_OPENAI_IMAGE_EDIT_NORMALIZATION,
)
)
url_search_params = '' url_search_params = ''
if image_config.IMAGES_EDIT_OPENAI_API_VERSION: if image_config.IMAGES_EDIT_OPENAI_API_VERSION: