1515import os
1616from pathlib import Path
1717
18+ from openai import OpenAI
19+
1820
1921# Configuration
2022MAX_INPUT_IMAGES = 3
@@ -56,24 +58,25 @@ def require_api_key():
5658def encode_image_to_data_url (path : Path ) -> str :
5759 if not path .exists ():
5860 raise SystemExit (f"Input image not found: { path } " )
59- mime , _ = mimetypes .guess_type (path . name )
61+ mime , _ = mimetypes .guess_type (str ( path ) )
6062 if not mime :
6163 mime = "image/png"
6264 data = path .read_bytes ()
6365 encoded = base64 .b64encode (data ).decode ("utf-8" )
6466 return f"data:{ mime } ;base64,{ encoded } "
6567
6668
67- def build_message_content (prompt : str , input_images ) :
68- content = [{"type" : "text" , "text" : prompt }]
69+ def build_message_content (prompt : str , input_images : list [ str ]) -> list [ dict ] :
70+ content : list [ dict ] = [{"type" : "text" , "text" : prompt }]
6971 for image_path in input_images :
7072 data_url = encode_image_to_data_url (Path (image_path ))
7173 content .append ({"type" : "image_url" , "image_url" : {"url" : data_url }})
7274 return content
7375
74- def parse_data_url (data_url : str ):
76+
77+ def parse_data_url (data_url : str ) -> tuple [str , bytes ]:
7578 if not data_url .startswith ("data:" ) or ";base64," not in data_url :
76- raise ValueError ("Image URL is not a base64 data URL." )
79+ raise SystemExit ("Image URL is not a base64 data URL." )
7780 header , encoded = data_url .split ("," , 1 )
7881 mime = header [5 :].split (";" , 1 )[0 ]
7982 try :
@@ -83,35 +86,27 @@ def parse_data_url(data_url: str):
8386 return mime , raw
8487
8588
86- def resolve_output_paths (filename : str , image_count : int , mime : str ):
87- output_path = Path (filename )
88- suffix = output_path .suffix
89-
90- # Validate/correct suffix matches MIME type
91- expected_suffix = MIME_TO_EXT .get (mime , ".png" )
92- if suffix and suffix .lower () != expected_suffix .lower ():
93- print (f"Warning: filename extension '{ suffix } ' doesn't match returned MIME type '{ mime } '. Using '{ expected_suffix } ' instead." )
94- suffix = expected_suffix
95- elif not suffix :
96- suffix = expected_suffix
97-
98- output_path = output_path .with_suffix (suffix )
89+ def resolve_output_path (filename : str , image_index : int , total_count : int , mime : str ) -> Path :
90+ output_path = Path (filename )
91+ suffix = output_path .suffix
9992
100- # Create parent directory if it doesn't exist (for paths with parent directories, absolute or relative)
101- if output_path .parent and str (output_path .parent ) != '.' :
102- output_path .parent .mkdir (parents = True , exist_ok = True )
93+ # Validate/correct suffix matches MIME type
94+ expected_suffix = MIME_TO_EXT .get (mime , ".png" )
95+ if suffix and suffix .lower () != expected_suffix .lower ():
96+ print (f"Warning: filename extension '{ suffix } ' doesn't match returned MIME type '{ mime } '. Using '{ expected_suffix } ' instead." )
97+ suffix = expected_suffix
98+ elif not suffix :
99+ suffix = expected_suffix
103100
104- if image_count == 1 :
105- return [output_path ]
101+ # Single image: use original stem + corrected suffix
102+ if total_count <= 1 :
103+ return output_path .with_suffix (suffix )
106104
107- paths = []
108- for index in range (image_count ):
109- numbered = output_path .with_name (f"{ output_path .stem } -{ index + 1 } { suffix } " )
110- paths .append (numbered )
111- return paths
105+ # Multiple images: append numbering
106+ return output_path .with_name (f"{ output_path .stem } -{ image_index + 1 } { suffix } " )
112107
113108
114- def extract_image_url (image ) :
109+ def extract_image_url (image : dict | object ) -> str | None :
115110 if isinstance (image , dict ):
116111 return image .get ("image_url" , {}).get ("url" ) or image .get ("url" )
117112 return None
@@ -123,7 +118,7 @@ def load_system_prompt():
123118 template_path = script_dir / "assets" / "SYSTEM_TEMPLATE"
124119
125120 if template_path .exists ():
126- content = template_path .read_text ().strip ()
121+ content = template_path .read_text (encoding = "utf-8" ).strip ()
127122 if content :
128123 return content
129124 return None
@@ -135,9 +130,8 @@ def main():
135130 if len (args .input_image ) > MAX_INPUT_IMAGES :
136131 raise SystemExit (f"Too many input images: { len (args .input_image )} (max { MAX_INPUT_IMAGES } )." )
137132
138- image_size = args .resolution or "1K"
133+ image_size = args .resolution
139134
140- from openai import OpenAI
141135 client = OpenAI (base_url = "https://openrouter.ai/api/v1" , api_key = require_api_key ())
142136
143137 # Build messages with optional system prompt
@@ -173,19 +167,18 @@ def main():
173167 if not images :
174168 raise SystemExit ("No images returned by the API." )
175169
176- first_url = extract_image_url (images [0 ])
177- if not first_url :
178- raise SystemExit ("Image payload missing image_url.url." )
179- first_mime , _ = parse_data_url (first_url )
180- output_paths = resolve_output_paths (args .filename , len (images ), first_mime )
170+ # Create output directory once before processing images
171+ output_base_path = Path (args .filename )
172+ if output_base_path .parent and str (output_base_path .parent ) != '.' :
173+ output_base_path .parent .mkdir (parents = True , exist_ok = True )
181174
182175 saved_paths = []
183176 for idx , image in enumerate (images ):
184177 image_url = extract_image_url (image )
185178 if not image_url :
186179 raise SystemExit ("Image payload missing image_url.url." )
187- _ , raw = parse_data_url (image_url )
188- output_path = output_paths [ idx ]
180+ mime , raw = parse_data_url (image_url )
181+ output_path = resolve_output_path ( args . filename , idx , len ( images ), mime )
189182 output_path .write_bytes (raw )
190183 saved_paths .append (output_path .resolve ())
191184
0 commit comments