Recursive batch img2img.py
diff --git a/modules/img2img.py b/modules/img2img.py
index a5f1c148a90..3b83814bf25 100644
--- a/modules/img2img.py
+++ b/modules/img2img.py
@@ -18,7 +18,13 @@
 def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=False, scale_by=1.0, use_png_info=False, png_info_props=None, png_info_dir=None):
     processing.fix_seed(p)
 
-    images = shared.listfiles(input_dir)
+    images = []
+    for root, directories, files in os.walk(input_dir):
+        for filename in files:
+            filepath = os.path.join(root, filename)
+            if filepath.endswith(".jpg") or filepath.endswith(".jpeg") or filepath.endswith(".png") or filepath.endswith(".webp"):
+                images.append(filepath)
+
 
     is_inpaint_batch = False
     if inpaint_mask_dir:
@@ -113,16 +119,17 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal
 
         for n, processed_image in enumerate(proc.images):
             filename = image_path.name
+            relpath = os.path.dirname(os.path.relpath(image, input_dir))
 
             if n > 0:
                 left, right = os.path.splitext(filename)
                 filename = f"{left}-{n}{right}"
 
             if not save_normally:
-                os.makedirs(output_dir, exist_ok=True)
+                os.makedirs(os.path.join(output_dir, relpath), exist_ok=True)
                 if processed_image.mode == 'RGBA':
                     processed_image = processed_image.convert("RGB")
-                processed_image.save(os.path.join(output_dir, filename))
+                processed_image.save(os.path.join(output_dir, relpath, filename))
 
 
 def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_styles, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, steps: int, sampler_index: int, mask_blur: int, mask_alpha: float, inpainting_fill: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, selected_scale_tab: int, height: int, width: int, scale_by: float, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, img2img_batch_inpaint_mask_dir: str, override_settings_texts, img2img_batch_use_png_info: bool, img2img_batch_png_info_props: list, img2img_batch_png_info_dir: str, request: gr.Request, *args):

Searches sub directories and performs img2img batch processing, also limits inputs to jpg, webp, and png. Then saves to output directory with relative paths.

Made this for personal use, but don't see why it isn't the default case as far as subdirectories. Can certainly see the image format limiter being an issue but was necessary to filter out directories the way it was written and didn't bother to change to a more generic isfile check.

 - OS: windows
 - Browser: chrome
 - Graphics card: nvidia 4090
