main.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. from typing import NamedTuple, Type, Dict, Any
  2. from modules import shared, script_callbacks, scripts
  3. from modules.shared import opts
  4. import gradio as gr
  5. import json
  6. from pathlib import Path
  7. from collections import namedtuple
  8. import scripts.ui as ui
  9. from scripts.dte_instance import dte_instance
  10. # ================================================================
  11. # General Callbacks
  12. # ================================================================
  13. CONFIG_PATH = Path(scripts.basedir(), 'config.json')
  14. SortBy = dte_instance.SortBy
  15. SortOrder = dte_instance.SortOrder
  16. GeneralConfig = namedtuple('GeneralConfig', [
  17. 'backup',
  18. 'dataset_dir',
  19. 'caption_ext',
  20. 'load_recursive',
  21. 'load_caption_from_filename',
  22. 'replace_new_line',
  23. 'use_interrogator',
  24. 'use_interrogator_names',
  25. 'use_custom_threshold_booru',
  26. 'custom_threshold_booru',
  27. 'use_custom_threshold_waifu',
  28. 'custom_threshold_waifu',
  29. 'save_kohya_metadata',
  30. 'meta_output_path',
  31. 'meta_input_path',
  32. 'meta_overwrite',
  33. 'meta_save_as_caption',
  34. 'meta_use_full_path'
  35. ])
  36. FilterConfig = namedtuple('FilterConfig', ['sw_prefix', 'sw_suffix', 'sw_regex','sort_by', 'sort_order', 'logic'])
  37. BatchEditConfig = namedtuple('BatchEditConfig', ['show_only_selected', 'prepend', 'use_regex', 'target', 'sw_prefix', 'sw_suffix', 'sw_regex', 'sory_by', 'sort_order', 'batch_sort_by', 'batch_sort_order', 'token_count'])
  38. EditSelectedConfig = namedtuple('EditSelectedConfig', ['auto_copy', 'sort_on_save', 'warn_change_not_saved', 'use_interrogator_name', 'sort_by', 'sort_order'])
  39. MoveDeleteConfig = namedtuple('MoveDeleteConfig', ['range', 'target', 'caption_ext', 'destination'])
  40. CFG_GENERAL_DEFAULT = GeneralConfig(True, '', '.txt', False, True, False, 'No', [], False, 0.7, False, 0.35, False, '', '', True, False, False)
  41. CFG_FILTER_P_DEFAULT = FilterConfig(False, False, False, SortBy.ALPHA.value, SortOrder.ASC.value, 'AND')
  42. CFG_FILTER_N_DEFAULT = FilterConfig(False, False, False, SortBy.ALPHA.value, SortOrder.ASC.value, 'OR')
  43. CFG_BATCH_EDIT_DEFAULT = BatchEditConfig(True, False, False, 'Only Selected Tags', False, False, False, SortBy.ALPHA.value, SortOrder.ASC.value, SortBy.ALPHA.value, SortOrder.ASC.value, 75)
  44. CFG_EDIT_SELECTED_DEFAULT = EditSelectedConfig(False, False, False, '', SortBy.ALPHA.value, SortOrder.ASC.value)
  45. CFG_MOVE_DELETE_DEFAULT = MoveDeleteConfig('Selected One', [], '.txt', '')
  46. class Config:
  47. def __init__(self):
  48. self.config = dict()
  49. def load(self):
  50. if not CONFIG_PATH.is_file():
  51. self.config = dict()
  52. return
  53. try:
  54. self.config = json.loads(CONFIG_PATH.read_text('utf8'))
  55. except:
  56. print('[tag-editor] Error on loading config.json. Default settings will be loaded.')
  57. self.config = dict()
  58. else:
  59. print('[tag-editor] Settings has been read from config.json')
  60. def save(self):
  61. CONFIG_PATH.write_text(json.dumps(self.config, indent=4), 'utf8')
  62. def read(self, name: str):
  63. return self.config.get(name)
  64. def write(self, cfg: dict, name: str):
  65. self.config[name] = cfg
  66. config = Config()
  67. def write_general_config(*args):
  68. cfg = GeneralConfig(*args)
  69. config.write(cfg._asdict(), 'general')
  70. def write_filter_config(*args):
  71. hlen = len(args) // 2
  72. cfg_p = FilterConfig(*args[:hlen])
  73. cfg_n = FilterConfig(*args[hlen:])
  74. config.write({'positive':cfg_p._asdict(), 'negative':cfg_n._asdict()}, 'filter')
  75. def write_batch_edit_config(*args):
  76. cfg = BatchEditConfig(*args)
  77. config.write(cfg._asdict(), 'batch_edit')
  78. def write_edit_selected_config(*args):
  79. cfg = EditSelectedConfig(*args)
  80. config.write(cfg._asdict(), 'edit_selected')
  81. def write_move_delete_config(*args):
  82. cfg = MoveDeleteConfig(*args)
  83. config.write(cfg._asdict(), 'file_move_delete')
  84. def read_config(name: str, config_type: Type, default: NamedTuple, compat_func = None):
  85. d = config.read(name)
  86. cfg = default
  87. if d:
  88. if compat_func: d = compat_func(d)
  89. d = cfg._asdict() | d
  90. d = {k:v for k,v in d.items() if k in cfg._asdict().keys()}
  91. cfg = config_type(**d)
  92. return cfg
  93. def read_general_config():
  94. # for compatibility
  95. generalcfg_intterogator_names = [
  96. ('use_blip_to_prefill', 'BLIP'),
  97. ('use_git_to_prefill', 'GIT-large-COCO'),
  98. ('use_booru_to_prefill', 'DeepDanbooru'),
  99. ('use_waifu_to_prefill', 'wd-v1-4-vit-tagger')
  100. ]
  101. use_interrogator_names = []
  102. def compat_func(d: Dict[str, Any]):
  103. if 'use_interrogator_names' in d.keys():
  104. return d
  105. for cfg in generalcfg_intterogator_names:
  106. if d.get(cfg[0]):
  107. use_interrogator_names.append(cfg[1])
  108. d['use_interrogator_names'] = use_interrogator_names
  109. return d
  110. return read_config('general', GeneralConfig, CFG_GENERAL_DEFAULT, compat_func)
  111. def read_filter_config():
  112. d = config.read('filter')
  113. d_p = d.get('positive') if d else None
  114. d_n = d.get('negative') if d else None
  115. cfg_p = CFG_FILTER_P_DEFAULT
  116. cfg_n = CFG_FILTER_N_DEFAULT
  117. if d_p:
  118. d_p = cfg_p._asdict() | d_p
  119. d_p = {k:v for k,v in d_p.items() if k in cfg_p._asdict().keys()}
  120. cfg_p = FilterConfig(**d_p)
  121. if d_n:
  122. d_n = cfg_n._asdict() | d_n
  123. d_n = {k:v for k,v in d_n.items() if k in cfg_n._asdict().keys()}
  124. cfg_n = FilterConfig(**d_n)
  125. return cfg_p, cfg_n
  126. def read_batch_edit_config():
  127. return read_config('batch_edit', BatchEditConfig, CFG_BATCH_EDIT_DEFAULT)
  128. def read_edit_selected_config():
  129. return read_config('edit_selected', EditSelectedConfig, CFG_EDIT_SELECTED_DEFAULT)
  130. def read_move_delete_config():
  131. return read_config('file_move_delete', MoveDeleteConfig, CFG_MOVE_DELETE_DEFAULT)
  132. # ================================================================
  133. # General Callbacks for Updating UIs
  134. # ================================================================
  135. def get_filters():
  136. filters = [ui.filter_by_tags.tag_filter_ui.get_filter(), ui.filter_by_tags.tag_filter_ui_neg.get_filter()] + [ui.filter_by_selection.path_filter]
  137. return filters
  138. def update_gallery():
  139. img_indices = ui.dte_instance.get_filtered_imgindices(filters=get_filters())
  140. total_image_num = len(ui.dte_instance.dataset)
  141. displayed_image_num = len(img_indices)
  142. ui.gallery_state.register_value('Displayed Images', f'{displayed_image_num} / {total_image_num} total')
  143. ui.gallery_state.register_value('Current Tag Filter', f"{ui.filter_by_tags.tag_filter_ui.get_filter()} {' AND ' if ui.filter_by_tags.tag_filter_ui.get_filter().tags and ui.filter_by_tags.tag_filter_ui_neg.get_filter().tags else ''} {ui.filter_by_tags.tag_filter_ui_neg.get_filter()}")
  144. ui.gallery_state.register_value('Current Selection Filter', f'{len(ui.filter_by_selection.path_filter.paths)} images')
  145. return [
  146. [str(i) for i in img_indices],
  147. 1,
  148. -1,
  149. -1,
  150. -1,
  151. ui.gallery_state.get_current_gallery_txt()
  152. ]
  153. def update_filter_and_gallery():
  154. return \
  155. [ui.filter_by_tags.tag_filter_ui.cbg_tags_update(), ui.filter_by_tags.tag_filter_ui_neg.cbg_tags_update()] +\
  156. update_gallery() +\
  157. ui.batch_edit_captions.get_common_tags(get_filters, ui.filter_by_tags) +\
  158. [', '.join(ui.filter_by_tags.tag_filter_ui.filter.tags)] +\
  159. [ui.batch_edit_captions.tag_select_ui_remove.cbg_tags_update()] +\
  160. ['', '']
  161. # ================================================================
  162. # Script Callbacks
  163. # ================================================================
  164. def on_ui_tabs():
  165. config.load()
  166. cfg_general = read_general_config()
  167. cfg_filter_p, cfg_filter_n = read_filter_config()
  168. cfg_batch_edit = read_batch_edit_config()
  169. cfg_edit_selected = read_edit_selected_config()
  170. cfg_file_move_delete = read_move_delete_config()
  171. with gr.Blocks(analytics_enabled=False) as dataset_tag_editor_interface:
  172. gr.HTML(value="""
  173. This extension works well with text captions in comma-separated style (such as the tags generated by DeepBooru interrogator).
  174. """)
  175. ui.toprow.create_ui(cfg_general)
  176. with gr.Accordion(label='Reload/Save Settings (config.json)', open=False):
  177. with gr.Row():
  178. btn_reload_config_file = gr.Button(value='Reload settings')
  179. btn_save_setting_as_default = gr.Button(value='Save current settings')
  180. btn_restore_default = gr.Button(value='Restore settings to default')
  181. with gr.Row().style(equal_height=False):
  182. with gr.Column():
  183. ui.load_dataset.create_ui(cfg_general)
  184. ui.dataset_gallery.create_ui(opts.dataset_editor_image_columns)
  185. ui.gallery_state.create_ui()
  186. with gr.Tab(label='Filter by Tags'):
  187. ui.filter_by_tags.create_ui(cfg_filter_p, cfg_filter_n, get_filters)
  188. with gr.Tab(label='Filter by Selection'):
  189. ui.filter_by_selection.create_ui(opts.dataset_editor_image_columns)
  190. with gr.Tab(label='Batch Edit Captions'):
  191. ui.batch_edit_captions.create_ui(cfg_batch_edit, get_filters)
  192. with gr.Tab(label='Edit Caption of Selected Image'):
  193. ui.edit_caption_of_selected_image.create_ui(cfg_edit_selected)
  194. with gr.Tab(label='Move or Delete Files'):
  195. ui.move_or_delete_files.create_ui(cfg_file_move_delete)
  196. #----------------------------------------------------------------
  197. # General
  198. components_general = [
  199. ui.toprow.cb_backup, ui.load_dataset.tb_img_directory, ui.load_dataset.tb_caption_file_ext, ui.load_dataset.cb_load_recursive,
  200. ui.load_dataset.cb_load_caption_from_filename, ui.load_dataset.cb_replace_new_line_with_comma, ui.load_dataset.rb_use_interrogator, ui.load_dataset.dd_intterogator_names,
  201. ui.load_dataset.cb_use_custom_threshold_booru, ui.load_dataset.sl_custom_threshold_booru, ui.load_dataset.cb_use_custom_threshold_waifu, ui.load_dataset.sl_custom_threshold_waifu,
  202. ui.toprow.cb_save_kohya_metadata, ui.toprow.tb_metadata_output, ui.toprow.tb_metadata_input, ui.toprow.cb_metadata_overwrite, ui.toprow.cb_metadata_as_caption, ui.toprow.cb_metadata_use_fullpath
  203. ]
  204. components_filter = \
  205. [ui.filter_by_tags.tag_filter_ui.cb_prefix, ui.filter_by_tags.tag_filter_ui.cb_suffix, ui.filter_by_tags.tag_filter_ui.cb_regex, ui.filter_by_tags.tag_filter_ui.rb_sort_by, ui.filter_by_tags.tag_filter_ui.rb_sort_order, ui.filter_by_tags.tag_filter_ui.rb_logic] +\
  206. [ui.filter_by_tags.tag_filter_ui_neg.cb_prefix, ui.filter_by_tags.tag_filter_ui_neg.cb_suffix, ui.filter_by_tags.tag_filter_ui_neg.cb_regex, ui.filter_by_tags.tag_filter_ui_neg.rb_sort_by, ui.filter_by_tags.tag_filter_ui_neg.rb_sort_order, ui.filter_by_tags.tag_filter_ui_neg.rb_logic]
  207. components_batch_edit = [
  208. ui.batch_edit_captions.cb_show_only_tags_selected, ui.batch_edit_captions.cb_prepend_tags, ui.batch_edit_captions.cb_use_regex,
  209. ui.batch_edit_captions.rb_sr_replace_target,
  210. ui.batch_edit_captions.tag_select_ui_remove.cb_prefix, ui.batch_edit_captions.tag_select_ui_remove.cb_suffix, ui.batch_edit_captions.tag_select_ui_remove.cb_regex,
  211. ui.batch_edit_captions.tag_select_ui_remove.rb_sort_by, ui.batch_edit_captions.tag_select_ui_remove.rb_sort_order,
  212. ui.batch_edit_captions.rb_sort_by, ui.batch_edit_captions.rb_sort_order,
  213. ui.batch_edit_captions.nb_token_count
  214. ]
  215. components_edit_selected = [
  216. ui.edit_caption_of_selected_image.cb_copy_caption_automatically, ui.edit_caption_of_selected_image.cb_sort_caption_on_save,
  217. ui.edit_caption_of_selected_image.cb_ask_save_when_caption_changed, ui.edit_caption_of_selected_image.dd_intterogator_names_si,
  218. ui.edit_caption_of_selected_image.rb_sort_by, ui.edit_caption_of_selected_image.rb_sort_order
  219. ]
  220. components_move_delete = [
  221. ui.move_or_delete_files.rb_move_or_delete_target_data, ui.move_or_delete_files.cbg_move_or_delete_target_file,
  222. ui.move_or_delete_files.tb_move_or_delete_caption_ext, ui.move_or_delete_files.tb_move_or_delete_destination_dir
  223. ]
  224. configurable_components = components_general + components_filter + components_batch_edit + components_edit_selected + components_move_delete
  225. def reload_config_file():
  226. config.load()
  227. p, n = read_filter_config()
  228. print('[tag-editor] Reload config.json')
  229. return read_general_config() + p + n + read_batch_edit_config() + read_edit_selected_config() + read_move_delete_config()
  230. btn_reload_config_file.click(
  231. fn=reload_config_file,
  232. outputs=configurable_components
  233. )
  234. def save_settings_callback(*a):
  235. p = 0
  236. def inc(v):
  237. nonlocal p
  238. p += v
  239. return p
  240. write_general_config(*a[p:inc(len(components_general))])
  241. write_filter_config(*a[p:inc(len(components_filter))])
  242. write_batch_edit_config(*a[p:inc(len(components_batch_edit))])
  243. write_edit_selected_config(*a[p:inc(len(components_edit_selected))])
  244. write_move_delete_config(*a[p:])
  245. config.save()
  246. print('[tag-editor] Current settings have been saved into config.json')
  247. btn_save_setting_as_default.click(
  248. fn=save_settings_callback,
  249. inputs=configurable_components
  250. )
  251. def restore_default_settings():
  252. write_general_config(*CFG_GENERAL_DEFAULT)
  253. write_filter_config(*CFG_FILTER_P_DEFAULT, *CFG_FILTER_N_DEFAULT)
  254. write_batch_edit_config(*CFG_BATCH_EDIT_DEFAULT)
  255. write_edit_selected_config(*CFG_EDIT_SELECTED_DEFAULT)
  256. write_move_delete_config(*CFG_MOVE_DELETE_DEFAULT)
  257. print('[tag-editor] Restore default settings')
  258. return CFG_GENERAL_DEFAULT + CFG_FILTER_P_DEFAULT + CFG_FILTER_N_DEFAULT + CFG_BATCH_EDIT_DEFAULT + CFG_EDIT_SELECTED_DEFAULT + CFG_MOVE_DELETE_DEFAULT
  259. btn_restore_default.click(
  260. fn=restore_default_settings,
  261. outputs=configurable_components
  262. )
  263. o_update_gallery = [ui.dataset_gallery.cbg_hidden_dataset_filter, ui.dataset_gallery.nb_hidden_dataset_filter_apply, ui.dataset_gallery.nb_hidden_image_index, ui.dataset_gallery.nb_hidden_image_index_prev, ui.edit_caption_of_selected_image.nb_hidden_image_index_save_or_not, ui.gallery_state.txt_gallery]
  264. o_update_filter_and_gallery = \
  265. [ui.filter_by_tags.tag_filter_ui.cbg_tags, ui.filter_by_tags.tag_filter_ui_neg.cbg_tags] + \
  266. o_update_gallery + \
  267. [ui.batch_edit_captions.tb_common_tags, ui.batch_edit_captions.tb_edit_tags] + \
  268. [ui.batch_edit_captions.tb_sr_selected_tags] +\
  269. [ui.batch_edit_captions.tag_select_ui_remove.cbg_tags] +\
  270. [ui.edit_caption_of_selected_image.tb_caption, ui.edit_caption_of_selected_image.tb_edit_caption]
  271. ui.toprow.set_callbacks(ui.load_dataset)
  272. ui.load_dataset.set_callbacks(o_update_filter_and_gallery,ui.toprow, ui.dataset_gallery, ui.filter_by_tags, ui.filter_by_selection, ui.batch_edit_captions, update_filter_and_gallery)
  273. ui.dataset_gallery.set_callbacks(ui.load_dataset, ui.gallery_state, get_filters)
  274. ui.gallery_state.set_callbacks(ui.dataset_gallery)
  275. ui.filter_by_tags.set_callbacks(o_update_gallery, o_update_filter_and_gallery, ui.batch_edit_captions, ui.move_or_delete_files, update_gallery, update_filter_and_gallery, get_filters)
  276. ui.filter_by_selection.set_callbacks(o_update_filter_and_gallery, ui.dataset_gallery, ui.filter_by_tags, get_filters, update_filter_and_gallery)
  277. ui.batch_edit_captions.set_callbacks(o_update_filter_and_gallery, ui.load_dataset, ui.filter_by_tags, get_filters, update_filter_and_gallery)
  278. ui.edit_caption_of_selected_image.set_callbacks(o_update_filter_and_gallery, ui.dataset_gallery, ui.load_dataset, get_filters, update_filter_and_gallery)
  279. ui.move_or_delete_files.set_callbacks(o_update_filter_and_gallery, ui.dataset_gallery, ui.filter_by_tags, ui.batch_edit_captions, ui.filter_by_selection, ui.edit_caption_of_selected_image, get_filters, update_filter_and_gallery)
  280. return [(dataset_tag_editor_interface, "Dataset Tag Editor", "dataset_tag_editor_interface")]
  281. def on_ui_settings():
  282. section = ('dataset-tag-editor', "Dataset Tag Editor")
  283. shared.opts.add_option("dataset_editor_image_columns", shared.OptionInfo(6, "Number of columns on image gallery", section=section))
  284. shared.opts.add_option("dataset_editor_max_res", shared.OptionInfo(0, "Max resolution of temporary files", section=section))
  285. shared.opts.add_option("dataset_editor_use_temp_files", shared.OptionInfo(False, "Force image gallery to use temporary files", section=section))
  286. shared.opts.add_option("dataset_editor_use_raw_clip_token", shared.OptionInfo(True, "Use raw CLIP token to calculate token count (without emphasis or embeddings)", section=section))
  287. script_callbacks.on_ui_settings(on_ui_settings)
  288. script_callbacks.on_ui_tabs(on_ui_tabs)