yeq6x commited on
Commit
28ba4eb
·
1 Parent(s): fc9a363

Implement target prefix/suffix functionality in app.py and create_image_caption_json.py for improved image processing. Update UI to reflect changes, enhancing user experience with clearer labeling and file handling. Refactor related functions to accommodate new naming conventions.

Browse files
Files changed (2) hide show
  1. app.py +91 -72
  2. create_image_caption_json.py +10 -10
app.py CHANGED
@@ -193,6 +193,24 @@ def _list_checkpoints(out_dir: str, limit: int = 20) -> List[str]:
193
  return []
194
 
195
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  def _prepare_script(
197
  dataset_name: str,
198
  caption: str,
@@ -205,8 +223,8 @@ def _prepare_script(
205
  override_max_epochs: Optional[int] = None,
206
  override_save_every: Optional[int] = None,
207
  override_run_name: Optional[str] = None,
208
- main_prefix: Optional[str] = None,
209
- main_suffix: Optional[str] = None,
210
  control_prefixes: Optional[List[Optional[str]]] = None,
211
  control_suffixes: Optional[List[Optional[str]]] = None,
212
  ) -> Path:
@@ -300,10 +318,10 @@ def _prepare_script(
300
 
301
  # Inject prefix/suffix flags for metadata creation
302
  extra_lines: List[str] = []
303
- if (main_prefix or ""):
304
- extra_lines.append(f" --main_prefix {_bash_quote(main_prefix)} \\")
305
- if (main_suffix or ""):
306
- extra_lines.append(f" --main_suffix {_bash_quote(main_suffix)} \\")
307
  for i in range(8):
308
  pre = control_prefixes[i] if (control_prefixes and i < len(control_prefixes)) else None
309
  suf = control_suffixes[i] if (control_suffixes and i < len(control_suffixes)) else None
@@ -434,8 +452,8 @@ def run_training(
434
  output_name: str,
435
  caption: str,
436
  image_uploads: Any,
437
- main_prefix: str,
438
- main_suffix: str,
439
  control0_uploads: Any,
440
  ctrl0_prefix: str,
441
  ctrl0_suffix: str,
@@ -560,8 +578,8 @@ def run_training(
560
  override_max_epochs=max_epochs if max_epochs and max_epochs > 0 else None,
561
  override_save_every=save_every if save_every and save_every > 0 else None,
562
  override_run_name=output_name.strip(),
563
- main_prefix=(main_prefix or ""),
564
- main_suffix=(main_suffix or ""),
565
  control_prefixes=[ctrl0_prefix, ctrl1_prefix, ctrl2_prefix, ctrl3_prefix, ctrl4_prefix, ctrl5_prefix, ctrl6_prefix, ctrl7_prefix],
566
  control_suffixes=[ctrl0_suffix, ctrl1_suffix, ctrl2_suffix, ctrl3_suffix, ctrl4_suffix, ctrl5_suffix, ctrl6_suffix, ctrl7_suffix],
567
  )
@@ -609,6 +627,15 @@ def build_ui() -> gr.Blocks:
609
  with gr.Blocks(title="Qwen-Image-Edit: Trainer") as demo:
610
  gr.Markdown("""
611
  # Qwen-Image-Edit Trainer
 
 
 
 
 
 
 
 
 
612
  """)
613
 
614
  with gr.Row():
@@ -616,90 +643,71 @@ def build_ui() -> gr.Blocks:
616
  caption = gr.Textbox(label="CAPTION", placeholder="A photo of ...", lines=2)
617
 
618
  with gr.Row():
619
- images_input = gr.File(label="Upload main images", file_count="multiple", type="filepath", scale=3)
620
- try:
621
- with gr.Column(scale=1):
622
- main_prefix = gr.Textbox(label="Main prefix", placeholder="e.g., img_")
623
- main_suffix = gr.Textbox(label="Main suffix", placeholder="e.g., _v2")
624
- except Exception:
625
- # Fallback if Column is unavailable
626
- main_prefix = gr.Textbox(label="Main prefix", placeholder="e.g., img_")
627
- main_suffix = gr.Textbox(label="Main suffix", placeholder="e.g., _v2")
628
 
629
  # control_0 is required and shown outside the accordion
630
  with gr.Row():
631
- ctrl0_files = gr.File(label="Upload control_0 images (required)", file_count="multiple", type="filepath", scale=3)
632
- try:
633
- with gr.Column(scale=1):
634
- ctrl0_prefix = gr.Textbox(label="control_0 prefix", placeholder="")
635
- ctrl0_suffix = gr.Textbox(label="control_0 suffix", placeholder="")
636
- except Exception:
637
- ctrl0_prefix = gr.Textbox(label="control_0 prefix", placeholder="")
638
- ctrl0_suffix = gr.Textbox(label="control_0 suffix", placeholder="")
639
 
640
  # Optional controls start from 1, accordion closed by default
641
  with gr.Accordion("Optional control images (control_1..control_7)", open=False):
642
  with gr.Row():
643
- ctrl1_files = gr.File(label="Upload control_1 images", file_count="multiple", type="filepath", scale=3)
644
- try:
645
- with gr.Column(scale=1):
646
- ctrl1_prefix = gr.Textbox(label="control_1 prefix", placeholder="")
647
- ctrl1_suffix = gr.Textbox(label="control_1 suffix", placeholder="")
648
- except Exception:
649
  ctrl1_prefix = gr.Textbox(label="control_1 prefix", placeholder="")
650
  ctrl1_suffix = gr.Textbox(label="control_1 suffix", placeholder="")
651
  with gr.Row():
652
- ctrl2_files = gr.File(label="Upload control_2 images", file_count="multiple", type="filepath", scale=3)
653
- try:
654
- with gr.Column(scale=1):
655
- ctrl2_prefix = gr.Textbox(label="control_2 prefix", placeholder="")
656
- ctrl2_suffix = gr.Textbox(label="control_2 suffix", placeholder="")
657
- except Exception:
658
  ctrl2_prefix = gr.Textbox(label="control_2 prefix", placeholder="")
659
  ctrl2_suffix = gr.Textbox(label="control_2 suffix", placeholder="")
660
  with gr.Row():
661
- ctrl3_files = gr.File(label="Upload control_3 images", file_count="multiple", type="filepath", scale=3)
662
- try:
663
- with gr.Column(scale=1):
664
- ctrl3_prefix = gr.Textbox(label="control_3 prefix", placeholder="")
665
- ctrl3_suffix = gr.Textbox(label="control_3 suffix", placeholder="")
666
- except Exception:
667
  ctrl3_prefix = gr.Textbox(label="control_3 prefix", placeholder="")
668
  ctrl3_suffix = gr.Textbox(label="control_3 suffix", placeholder="")
669
  with gr.Row():
670
- ctrl4_files = gr.File(label="Upload control_4 images", file_count="multiple", type="filepath", scale=3)
671
- try:
672
- with gr.Column(scale=1):
673
- ctrl4_prefix = gr.Textbox(label="control_4 prefix", placeholder="")
674
- ctrl4_suffix = gr.Textbox(label="control_4 suffix", placeholder="")
675
- except Exception:
676
  ctrl4_prefix = gr.Textbox(label="control_4 prefix", placeholder="")
677
  ctrl4_suffix = gr.Textbox(label="control_4 suffix", placeholder="")
678
  with gr.Row():
679
- ctrl5_files = gr.File(label="Upload control_5 images", file_count="multiple", type="filepath", scale=3)
680
- try:
681
- with gr.Column(scale=1):
682
- ctrl5_prefix = gr.Textbox(label="control_5 prefix", placeholder="")
683
- ctrl5_suffix = gr.Textbox(label="control_5 suffix", placeholder="")
684
- except Exception:
685
  ctrl5_prefix = gr.Textbox(label="control_5 prefix", placeholder="")
686
  ctrl5_suffix = gr.Textbox(label="control_5 suffix", placeholder="")
687
  with gr.Row():
688
- ctrl6_files = gr.File(label="Upload control_6 images", file_count="multiple", type="filepath", scale=3)
689
- try:
690
- with gr.Column(scale=1):
691
- ctrl6_prefix = gr.Textbox(label="control_6 prefix", placeholder="")
692
- ctrl6_suffix = gr.Textbox(label="control_6 suffix", placeholder="")
693
- except Exception:
694
  ctrl6_prefix = gr.Textbox(label="control_6 prefix", placeholder="")
695
  ctrl6_suffix = gr.Textbox(label="control_6 suffix", placeholder="")
696
  with gr.Row():
697
- ctrl7_files = gr.File(label="Upload control_7 images", file_count="multiple", type="filepath", scale=3)
698
- try:
699
- with gr.Column(scale=1):
700
- ctrl7_prefix = gr.Textbox(label="control_7 prefix", placeholder="")
701
- ctrl7_suffix = gr.Textbox(label="control_7 suffix", placeholder="")
702
- except Exception:
703
  ctrl7_prefix = gr.Textbox(label="control_7 prefix", placeholder="")
704
  ctrl7_suffix = gr.Textbox(label="control_7 suffix", placeholder="")
705
 
@@ -708,16 +716,26 @@ def build_ui() -> gr.Blocks:
708
  run_btn = gr.Button("Start Training", variant="primary")
709
  logs = gr.Textbox(label="Logs", lines=20)
710
  ckpt_files = gr.Files(label="Checkpoints (live)", interactive=False)
711
- lora_file = gr.File(label="Download LoRA (latest)", interactive=False)
712
 
713
  with gr.Row():
714
  max_epochs = gr.Number(label="Max epochs (this run)", value=10, precision=0)
715
  save_every = gr.Number(label="Save every N epochs", value=5, precision=0)
716
 
 
 
 
 
 
 
 
 
 
 
 
717
  run_btn.click(
718
  fn=run_training,
719
  inputs=[
720
- output_name, caption, images_input, main_prefix, main_suffix,
721
  ctrl0_files, ctrl0_prefix, ctrl0_suffix,
722
  ctrl1_files, ctrl1_prefix, ctrl1_suffix,
723
  ctrl2_files, ctrl2_prefix, ctrl2_suffix,
@@ -728,7 +746,7 @@ def build_ui() -> gr.Blocks:
728
  ctrl7_files, ctrl7_prefix, ctrl7_suffix,
729
  max_epochs, save_every,
730
  ],
731
- outputs=[logs, ckpt_files, lora_file],
732
  )
733
 
734
  return demo
@@ -783,3 +801,4 @@ if __name__ == "__main__":
783
  except TypeError:
784
  # Older gradio without allowed_paths
785
  ui.launch(server_name="0.0.0.0")
 
 
193
  return []
194
 
195
 
196
+ def _files_to_gallery(files: Any) -> List[str]:
197
+ items: List[str] = []
198
+ if not files:
199
+ return items
200
+ seq = files if isinstance(files, (list, tuple)) else [files]
201
+ for f in seq:
202
+ p = None
203
+ if isinstance(f, str):
204
+ p = f
205
+ elif isinstance(f, dict):
206
+ p = f.get("path") or f.get("name")
207
+ else:
208
+ p = getattr(f, "path", None) or getattr(f, "name", None)
209
+ if p:
210
+ items.append(p)
211
+ return items
212
+
213
+
214
  def _prepare_script(
215
  dataset_name: str,
216
  caption: str,
 
223
  override_max_epochs: Optional[int] = None,
224
  override_save_every: Optional[int] = None,
225
  override_run_name: Optional[str] = None,
226
+ target_prefix: Optional[str] = None,
227
+ target_suffix: Optional[str] = None,
228
  control_prefixes: Optional[List[Optional[str]]] = None,
229
  control_suffixes: Optional[List[Optional[str]]] = None,
230
  ) -> Path:
 
318
 
319
  # Inject prefix/suffix flags for metadata creation
320
  extra_lines: List[str] = []
321
+ if (target_prefix or ""):
322
+ extra_lines.append(f" --target_prefix {_bash_quote(target_prefix)} \\")
323
+ if (target_suffix or ""):
324
+ extra_lines.append(f" --target_suffix {_bash_quote(target_suffix)} \\")
325
  for i in range(8):
326
  pre = control_prefixes[i] if (control_prefixes and i < len(control_prefixes)) else None
327
  suf = control_suffixes[i] if (control_suffixes and i < len(control_suffixes)) else None
 
452
  output_name: str,
453
  caption: str,
454
  image_uploads: Any,
455
+ target_prefix: str,
456
+ target_suffix: str,
457
  control0_uploads: Any,
458
  ctrl0_prefix: str,
459
  ctrl0_suffix: str,
 
578
  override_max_epochs=max_epochs if max_epochs and max_epochs > 0 else None,
579
  override_save_every=save_every if save_every and save_every > 0 else None,
580
  override_run_name=output_name.strip(),
581
+ target_prefix=(target_prefix or ""),
582
+ target_suffix=(target_suffix or ""),
583
  control_prefixes=[ctrl0_prefix, ctrl1_prefix, ctrl2_prefix, ctrl3_prefix, ctrl4_prefix, ctrl5_prefix, ctrl6_prefix, ctrl7_prefix],
584
  control_suffixes=[ctrl0_suffix, ctrl1_suffix, ctrl2_suffix, ctrl3_suffix, ctrl4_suffix, ctrl5_suffix, ctrl6_suffix, ctrl7_suffix],
585
  )
 
627
  with gr.Blocks(title="Qwen-Image-Edit: Trainer") as demo:
628
  gr.Markdown("""
629
  # Qwen-Image-Edit Trainer
630
+ このページでは、学習に使う画像をアップロードし、必要ならファイル名の前後にある共通の文字(prefix/suffix)を指定して、
631
+ 自動でデータセットを作成し学習を開始します。難しい操作は不要です。
632
+
633
+ 画像と名前の対応づけ
634
+ - prefix/suffix: ファイルの同名判定のため、画像のファイル名から共通の先頭/末尾文字を取り除く指定(例: IMG_ や _v2)
635
+ - まずメイン画像のファイル名(拡張子なし)から、指定した Target prefix/suffix を取り除いたものを key とします。
636
+ - 各コントロールは「付加」規則で、期待名 = control_prefix_i + key + control_suffix_i + ".png" を探して対応付けます。
637
+ - アップロード時に画像は自動で .png に変換して保存します(元のファイル名のベースは維持)。
638
+ - Control 0 は必須、Control 1〜7 は任意。コントロール画像が1枚だけのときは、すべてのメイン画像に適用します。
639
  """)
640
 
641
  with gr.Row():
 
643
  caption = gr.Textbox(label="CAPTION", placeholder="A photo of ...", lines=2)
644
 
645
  with gr.Row():
646
+ with gr.Column(scale=3):
647
+ images_input = gr.File(label="Upload target images", file_count="multiple", type="filepath")
648
+ target_gallery = gr.Gallery(label="Target preview", columns=4, height=200)
649
+ with gr.Column(scale=1):
650
+ target_prefix = gr.Textbox(label="Target prefix", placeholder="e.g., IMG_")
651
+ target_suffix = gr.Textbox(label="Target suffix", placeholder="e.g., _v2")
 
 
 
652
 
653
  # control_0 is required and shown outside the accordion
654
  with gr.Row():
655
+ with gr.Column(scale=3):
656
+ ctrl0_files = gr.File(label="Upload control_0 images (required)", file_count="multiple", type="filepath")
657
+ ctrl0_gallery = gr.Gallery(label="control_0 preview", columns=4, height=200)
658
+ with gr.Column(scale=1):
659
+ ctrl0_prefix = gr.Textbox(label="control_0 prefix", placeholder="e.g., C0_")
660
+ ctrl0_suffix = gr.Textbox(label="control_0 suffix", placeholder="e.g., _mask")
 
 
661
 
662
  # Optional controls start from 1, accordion closed by default
663
  with gr.Accordion("Optional control images (control_1..control_7)", open=False):
664
  with gr.Row():
665
+ with gr.Column(scale=3):
666
+ ctrl1_files = gr.File(label="Upload control_1 images", file_count="multiple", type="filepath")
667
+ ctrl1_gallery = gr.Gallery(label="control_1 preview", columns=4, height=200)
668
+ with gr.Column(scale=1):
 
 
669
  ctrl1_prefix = gr.Textbox(label="control_1 prefix", placeholder="")
670
  ctrl1_suffix = gr.Textbox(label="control_1 suffix", placeholder="")
671
  with gr.Row():
672
+ with gr.Column(scale=3):
673
+ ctrl2_files = gr.File(label="Upload control_2 images", file_count="multiple", type="filepath")
674
+ ctrl2_gallery = gr.Gallery(label="control_2 preview", columns=4, height=200)
675
+ with gr.Column(scale=1):
 
 
676
  ctrl2_prefix = gr.Textbox(label="control_2 prefix", placeholder="")
677
  ctrl2_suffix = gr.Textbox(label="control_2 suffix", placeholder="")
678
  with gr.Row():
679
+ with gr.Column(scale=3):
680
+ ctrl3_files = gr.File(label="Upload control_3 images", file_count="multiple", type="filepath")
681
+ ctrl3_gallery = gr.Gallery(label="control_3 preview", columns=4, height=200)
682
+ with gr.Column(scale=1):
 
 
683
  ctrl3_prefix = gr.Textbox(label="control_3 prefix", placeholder="")
684
  ctrl3_suffix = gr.Textbox(label="control_3 suffix", placeholder="")
685
  with gr.Row():
686
+ with gr.Column(scale=3):
687
+ ctrl4_files = gr.File(label="Upload control_4 images", file_count="multiple", type="filepath")
688
+ ctrl4_gallery = gr.Gallery(label="control_4 preview", columns=4, height=200)
689
+ with gr.Column(scale=1):
 
 
690
  ctrl4_prefix = gr.Textbox(label="control_4 prefix", placeholder="")
691
  ctrl4_suffix = gr.Textbox(label="control_4 suffix", placeholder="")
692
  with gr.Row():
693
+ with gr.Column(scale=3):
694
+ ctrl5_files = gr.File(label="Upload control_5 images", file_count="multiple", type="filepath")
695
+ ctrl5_gallery = gr.Gallery(label="control_5 preview", columns=4, height=200)
696
+ with gr.Column(scale=1):
 
 
697
  ctrl5_prefix = gr.Textbox(label="control_5 prefix", placeholder="")
698
  ctrl5_suffix = gr.Textbox(label="control_5 suffix", placeholder="")
699
  with gr.Row():
700
+ with gr.Column(scale=3):
701
+ ctrl6_files = gr.File(label="Upload control_6 images", file_count="multiple", type="filepath")
702
+ ctrl6_gallery = gr.Gallery(label="control_6 preview", columns=4, height=200)
703
+ with gr.Column(scale=1):
 
 
704
  ctrl6_prefix = gr.Textbox(label="control_6 prefix", placeholder="")
705
  ctrl6_suffix = gr.Textbox(label="control_6 suffix", placeholder="")
706
  with gr.Row():
707
+ with gr.Column(scale=3):
708
+ ctrl7_files = gr.File(label="Upload control_7 images", file_count="multiple", type="filepath")
709
+ ctrl7_gallery = gr.Gallery(label="control_7 preview", columns=4, height=200)
710
+ with gr.Column(scale=1):
 
 
711
  ctrl7_prefix = gr.Textbox(label="control_7 prefix", placeholder="")
712
  ctrl7_suffix = gr.Textbox(label="control_7 suffix", placeholder="")
713
 
 
716
  run_btn = gr.Button("Start Training", variant="primary")
717
  logs = gr.Textbox(label="Logs", lines=20)
718
  ckpt_files = gr.Files(label="Checkpoints (live)", interactive=False)
 
719
 
720
  with gr.Row():
721
  max_epochs = gr.Number(label="Max epochs (this run)", value=10, precision=0)
722
  save_every = gr.Number(label="Save every N epochs", value=5, precision=0)
723
 
724
+ # Wire previews
725
+ images_input.change(fn=_files_to_gallery, inputs=images_input, outputs=target_gallery)
726
+ ctrl0_files.change(fn=_files_to_gallery, inputs=ctrl0_files, outputs=ctrl0_gallery)
727
+ ctrl1_files.change(fn=_files_to_gallery, inputs=ctrl1_files, outputs=ctrl1_gallery)
728
+ ctrl2_files.change(fn=_files_to_gallery, inputs=ctrl2_files, outputs=ctrl2_gallery)
729
+ ctrl3_files.change(fn=_files_to_gallery, inputs=ctrl3_files, outputs=ctrl3_gallery)
730
+ ctrl4_files.change(fn=_files_to_gallery, inputs=ctrl4_files, outputs=ctrl4_gallery)
731
+ ctrl5_files.change(fn=_files_to_gallery, inputs=ctrl5_files, outputs=ctrl5_gallery)
732
+ ctrl6_files.change(fn=_files_to_gallery, inputs=ctrl6_files, outputs=ctrl6_gallery)
733
+ ctrl7_files.change(fn=_files_to_gallery, inputs=ctrl7_files, outputs=ctrl7_gallery)
734
+
735
  run_btn.click(
736
  fn=run_training,
737
  inputs=[
738
+ output_name, caption, images_input, target_prefix, target_suffix,
739
  ctrl0_files, ctrl0_prefix, ctrl0_suffix,
740
  ctrl1_files, ctrl1_prefix, ctrl1_suffix,
741
  ctrl2_files, ctrl2_prefix, ctrl2_suffix,
 
746
  ctrl7_files, ctrl7_prefix, ctrl7_suffix,
747
  max_epochs, save_every,
748
  ],
749
+ outputs=[logs, ckpt_files],
750
  )
751
 
752
  return demo
 
801
  except TypeError:
802
  # Older gradio without allowed_paths
803
  ui.launch(server_name="0.0.0.0")
804
+
create_image_caption_json.py CHANGED
@@ -87,8 +87,8 @@ def create_image_caption_json_unified(
87
  caption: str,
88
  output_json: str,
89
  *,
90
- main_prefix: str = "",
91
- main_suffix: str = "",
92
  control_prefixes: Optional[List[Optional[str]]] = None,
93
  control_suffixes: Optional[List[Optional[str]]] = None,
94
  allow_single: bool = True,
@@ -105,7 +105,7 @@ def create_image_caption_json_unified(
105
  filenames = _list_images(input_folder)
106
 
107
  # If no prefixes/suffixes provided, keep strict validation behavior
108
- use_name_matching = bool(main_prefix or main_suffix)
109
  if control_prefixes:
110
  use_name_matching = True
111
 
@@ -124,8 +124,8 @@ def create_image_caption_json_unified(
124
  f.write(json.dumps(entry, ensure_ascii=False) + '\n')
125
  count += 1
126
  else:
127
- # Build main keys
128
- main_keys = [_normalize(fn, main_prefix, main_suffix) for fn in filenames]
129
 
130
  # Write matched entries by constructing expected control names using add-rule
131
  for idx, fname in enumerate(filenames):
@@ -134,7 +134,7 @@ def create_image_caption_json_unified(
134
  "caption": caption,
135
  }
136
  present_count = 0
137
- mkey = main_keys[idx]
138
  for i, cdir in enumerate(control_dirs):
139
  if not cdir:
140
  continue
@@ -168,8 +168,8 @@ if __name__ == "__main__":
168
  parser.add_argument('-o', '--output-json', default='metadata.jsonl', help='出力JSONLパス(既定: metadata.jsonl)')
169
  parser.add_argument('--image-dir', default='/workspace/data/image', help='image_pathの親ディレクトリパス(JSON出力用)')
170
 
171
- parser.add_argument('--main_prefix', default=os.environ.get('MAIN_PREFIX', ''), help='メイン画像名から削除するプレフィックス')
172
- parser.add_argument('--main_suffix', default=os.environ.get('MAIN_SUFFIX', ''), help='メイン画像名から削除するサフィックス')
173
 
174
  # 最大 control_dir_0..7 まで受け付け
175
  for i in range(8):
@@ -211,8 +211,8 @@ if __name__ == "__main__":
211
  control_dirs=control_dirs,
212
  caption=args.caption,
213
  output_json=args.output_json,
214
- main_prefix=args.main_prefix,
215
- main_suffix=args.main_suffix,
216
  control_prefixes=control_prefixes,
217
  control_suffixes=control_suffixes,
218
  allow_single=args.allow_single,
 
87
  caption: str,
88
  output_json: str,
89
  *,
90
+ target_prefix: str = "",
91
+ target_suffix: str = "",
92
  control_prefixes: Optional[List[Optional[str]]] = None,
93
  control_suffixes: Optional[List[Optional[str]]] = None,
94
  allow_single: bool = True,
 
105
  filenames = _list_images(input_folder)
106
 
107
  # If no prefixes/suffixes provided, keep strict validation behavior
108
+ use_name_matching = bool(target_prefix or target_suffix)
109
  if control_prefixes:
110
  use_name_matching = True
111
 
 
124
  f.write(json.dumps(entry, ensure_ascii=False) + '\n')
125
  count += 1
126
  else:
127
+ # Build target keys
128
+ target_keys = [_normalize(fn, target_prefix, target_suffix) for fn in filenames]
129
 
130
  # Write matched entries by constructing expected control names using add-rule
131
  for idx, fname in enumerate(filenames):
 
134
  "caption": caption,
135
  }
136
  present_count = 0
137
+ mkey = target_keys[idx]
138
  for i, cdir in enumerate(control_dirs):
139
  if not cdir:
140
  continue
 
168
  parser.add_argument('-o', '--output-json', default='metadata.jsonl', help='出力JSONLパス(既定: metadata.jsonl)')
169
  parser.add_argument('--image-dir', default='/workspace/data/image', help='image_pathの親ディレクトリパス(JSON出力用)')
170
 
171
+ parser.add_argument('--target_prefix', default=os.environ.get('TARGET_PREFIX', ''), help='ターゲット画像名から削除するプレフィックス')
172
+ parser.add_argument('--target_suffix', default=os.environ.get('TARGET_SUFFIX', ''), help='ターゲット画像名から削除するサフィックス')
173
 
174
  # 最大 control_dir_0..7 まで受け付け
175
  for i in range(8):
 
211
  control_dirs=control_dirs,
212
  caption=args.caption,
213
  output_json=args.output_json,
214
+ target_prefix=args.target_prefix,
215
+ target_suffix=args.target_suffix,
216
  control_prefixes=control_prefixes,
217
  control_suffixes=control_suffixes,
218
  allow_single=args.allow_single,