1111import tempfile
1212import os
1313import uuid
14+ from pathlib import Path
1415
1516from backend .models .images import (
1617 ImageGenerationRequest ,
4748logger = logging .getLogger (__name__ )
4849
4950
51+ def normalize_filename (filename : str ) -> str :
52+ """
53+ Normalize a filename to be safe for file systems.
54+
55+ Args:
56+ filename: The filename to normalize
57+
58+ Returns:
59+ A normalized filename safe for most file systems
60+ """
61+ if not filename :
62+ return filename
63+
64+ # Use pathlib to handle the filename safely
65+ path = Path (filename )
66+
67+ # Get the stem (filename without extension) and suffix (extension)
68+ stem = path .stem
69+ suffix = path .suffix
70+
71+ # Remove or replace invalid characters for most filesystems
72+ # Keep alphanumeric, hyphens, underscores, and dots
73+ stem = re .sub (r'[^a-zA-Z0-9_\-.]' , '_' , stem )
74+
75+ # Remove multiple consecutive underscores
76+ stem = re .sub (r'_+' , '_' , stem )
77+
78+ # Remove leading/trailing underscores and dots
79+ stem = stem .strip ('_.' )
80+
81+ # Ensure the filename isn't empty
82+ if not stem :
83+ stem = "generated_image"
84+
85+ # Reconstruct the filename
86+ normalized = f"{ stem } { suffix } " if suffix else stem
87+
88+ # Ensure the filename isn't too long (most filesystems support 255 chars)
89+ if len (normalized ) > 200 : # Leave some room for additional suffixes
90+ # Truncate the stem but keep the extension
91+ max_stem_length = 200 - len (suffix )
92+ stem = stem [:max_stem_length ]
93+ normalized = f"{ stem } { suffix } " if suffix else stem
94+
95+ return normalized
96+
97+
98+ async def generate_filename_for_prompt (prompt : str , extension : str = None ) -> str :
99+ """
100+ Generate a filename using the existing filename generation endpoint.
101+
102+ Args:
103+ prompt: The prompt used for image generation
104+ extension: File extension (e.g., '.png', '.jpg')
105+
106+ Returns:
107+ Generated filename or None if generation fails
108+ """
109+ try :
110+ # Create request for filename generation
111+ filename_request = ImageFilenameGenerateRequest (
112+ prompt = prompt ,
113+ extension = extension
114+ )
115+
116+ # Call the filename generation function directly
117+ filename_response = generate_image_filename (filename_request )
118+
119+ # Normalize the generated filename
120+ generated_filename = normalize_filename (filename_response .filename )
121+
122+ return generated_filename
123+
124+ except Exception as e :
125+ return None
126+
127+
50128@router .post ("/generate" , response_model = ImageGenerationResponse )
51129async def generate_image (request : ImageGenerationRequest ):
52130 """Generate an image based on the provided prompt and settings"""
@@ -74,9 +152,6 @@ async def generate_image(request: ImageGenerationRequest):
74152 if request .user :
75153 params ["user" ] = request .user
76154
77- logger .info (
78- f"Generating image with gpt-image-1, quality: { request .quality } , size: { request .size } " )
79-
80155 # Generate image
81156 response = dalle_client .generate_image (** params )
82157
@@ -99,10 +174,6 @@ async def generate_image(request: ImageGenerationRequest):
99174 input_tokens_details = input_tokens_details
100175 )
101176
102- # Log token usage for cost tracking
103- logger .info (
104- f"Token usage - Total: { token_usage .total_tokens } , Input: { token_usage .input_tokens } , Output: { token_usage .output_tokens } " )
105-
106177 return ImageGenerationResponse (
107178 success = True ,
108179 message = "Refer to the imgen_model_response for details" ,
@@ -145,19 +216,12 @@ async def edit_image(request: ImageEditRequest):
145216 if request .user :
146217 params ["user" ] = request .user
147218
148- # Log information about multiple images if applicable
219+ # Check if organization is verified when using multiple images
149220 if isinstance (request .image , list ):
150221 image_count = len (request .image )
151- logger .info (
152- f"Editing with { image_count } reference images using gpt-image-1, quality: { request .quality } , size: { request .size } " )
153-
154- # Check if organization is verified when using multiple images
155222 if image_count > 1 and not settings .OPENAI_ORG_VERIFIED :
156223 logger .warning (
157224 "Using multiple reference images requires organization verification" )
158- else :
159- logger .info (
160- f"Editing single image using gpt-image-1, quality: { request .quality } , size: { request .size } " )
161225
162226 # Perform image editing
163227 response = dalle_client .edit_image (** params )
@@ -209,10 +273,6 @@ async def edit_image_upload(
209273):
210274 """Edit input images uploaded via multipart form data"""
211275 try :
212- # Log request info
213- logger .info (
214- f"Received { len (image )} image(s) for editing with prompt: { prompt } " )
215-
216276 # Validate file size for all images
217277 max_file_size_mb = settings .GPT_IMAGE_MAX_FILE_SIZE_MB
218278 temp_files = []
@@ -477,10 +537,32 @@ async def save_generated_images(
477537 # Reset file pointer
478538 img_file .seek (0 )
479539
480- # Create filename
481- quality_suffix = f"_{ request .quality } " if request .model == "gpt-image-1" and hasattr (
482- request , "quality" ) else ""
483- filename = f"generated_image_{ idx + 1 } { quality_suffix } .{ img_format .lower ()} "
540+ # Generate intelligent filename using the existing endpoint
541+ if request .prompt :
542+ filename = await generate_filename_for_prompt (
543+ request .prompt ,
544+ f".{ img_format .lower ()} "
545+ )
546+
547+ # Add index suffix for multiple images
548+ if filename and len (images_data ) > 1 :
549+ # Insert index before the extension
550+ path = Path (filename )
551+ stem = path .stem
552+ suffix = path .suffix
553+ filename = f"{ stem } _{ idx + 1 } { suffix } "
554+ logger .info (
555+ f"Using generated filename with index: { filename } " )
556+ elif filename :
557+ logger .info (f"Using generated filename: { filename } " )
558+
559+ # Fallback to default naming if filename generation fails
560+ if not filename :
561+ quality_suffix = f"_{ request .quality } " if request .model == "gpt-image-1" and hasattr (
562+ request , "quality" ) else ""
563+ filename = f"generated_image_{ idx + 1 } { quality_suffix } .{ img_format .lower ()} "
564+ filename = normalize_filename (filename )
565+ logger .info (f"Using fallback filename: { filename } " )
484566
485567 elif "url" in img_data :
486568 # Download image from URL
@@ -507,10 +589,27 @@ async def save_generated_images(
507589 # Reset file pointer
508590 img_file .seek (0 )
509591
510- # Create filename
511- quality_suffix = f"_{ request .quality } " if request .model == "gpt-image-1" and hasattr (
512- request , "quality" ) else ""
513- filename = f"generated_image_{ idx + 1 } { quality_suffix } .{ ext } "
592+ # Generate intelligent filename using the existing endpoint
593+ if request .prompt :
594+ filename = await generate_filename_for_prompt (
595+ request .prompt ,
596+ f".{ ext } "
597+ )
598+
599+ # Add index suffix for multiple images
600+ if filename and len (images_data ) > 1 :
601+ # Insert index before the extension
602+ path = Path (filename )
603+ stem = path .stem
604+ suffix = path .suffix
605+ filename = f"{ stem } _{ idx + 1 } { suffix } "
606+
607+ # Fallback to default naming if filename generation fails
608+ if not filename :
609+ quality_suffix = f"_{ request .quality } " if request .model == "gpt-image-1" and hasattr (
610+ request , "quality" ) else ""
611+ filename = f"generated_image_{ idx + 1 } { quality_suffix } .{ ext } "
612+ filename = normalize_filename (filename )
514613 else :
515614 logger .warning (
516615 f"Unsupported image data format for image { idx + 1 } " )
@@ -627,7 +726,6 @@ def analyze_image(req: ImageAnalyzeRequest):
627726 file_path += f"?{ image_sas_token } "
628727
629728 # Download the image from the URL
630- logger .info (f"Downloading image for analysis from: { file_path } " )
631729 response = requests .get (file_path , timeout = 30 )
632730 if response .status_code != 200 :
633731 raise HTTPException (
@@ -640,7 +738,6 @@ def analyze_image(req: ImageAnalyzeRequest):
640738
641739 # Option 2: Process from base64 string
642740 elif req .base64_image :
643- logger .info ("Processing image from base64 data" )
644741 try :
645742 # Decode base64 to binary
646743 image_content = base64 .b64decode (req .base64_image )
@@ -658,8 +755,6 @@ def analyze_image(req: ImageAnalyzeRequest):
658755 has_transparency = img .mode == 'RGBA' and 'A' in img .getbands ()
659756
660757 if has_transparency :
661- logger .info (
662- "Image has transparency, converting for analysis" )
663758 # Create a white background
664759 background = Image .new (
665760 'RGBA' , img .size , (255 , 255 , 255 , 255 ))
@@ -678,8 +773,6 @@ def analyze_image(req: ImageAnalyzeRequest):
678773 # This is optional but can help with very large images
679774 width , height = img .size
680775 if width > 1500 or height > 1500 :
681- logger .info (
682- f"Image is large ({ width } x{ height } ), resizing for analysis" )
683776 # Calculate new dimensions
684777 max_dimension = 1500
685778 if width > height :
@@ -713,7 +806,6 @@ def analyze_image(req: ImageAnalyzeRequest):
713806 image_base64 = re .sub (r"^data:image/.+;base64," , "" , image_base64 )
714807
715808 # analyze the image using the LLM
716- logger .info ("Sending image to LLM for analysis" )
717809 image_analyzer = ImageAnalyzer (llm_client , settings .LLM_DEPLOYMENT )
718810 insights = image_analyzer .image_chat (
719811 image_base64 , analyze_image_system_message )
@@ -774,17 +866,12 @@ def protect_image_prompt(req: ImagePromptBrandProtectionRequest):
774866 try :
775867 if req .brands_to_protect :
776868 if req .protection_mode == "replace" :
777- logger .info (
778- f"Replace competitor brands of: { req .brands_to_protect } " )
779869 system_message = brand_protect_replace_msg .format (
780870 brands = req .brands_to_protect )
781871 elif req .protection_mode == "neutralize" :
782- logger .info (
783- f"Neutralize competitor brands of: { req .brands_to_protect } " )
784872 system_message = brand_protect_neutralize_msg .format (
785873 brands = req .brands_to_protect )
786874 else :
787- logger .info (f"No brand protection specified." )
788875 return ImagePromptBrandProtectionResponse (enhanced_prompt = req .original_prompt )
789876
790877 # Ensure LLM client is available
0 commit comments