from typing import List, NamedTuple, Type, Dict, Any from modules import shared, script_callbacks, scripts from modules.shared import opts import gradio as gr import json import os from collections import namedtuple from scripts.dynamic_import import dynamic_import ui = dynamic_import('scripts/dataset_tag_editor/ui.py') filters = ui.filters dte = ui.dte dataset_tag_editor = dte.DatasetTagEditor() path_filter = filters.PathFilter() tag_filter_ui:ui.TagFilterUI = None tag_filter_ui_neg:ui.TagFilterUI = None tag_select_ui_remove:ui.TagSelectUI = None def get_filters(): return [path_filter, tag_filter_ui.get_filter(), tag_filter_ui_neg.get_filter()] total_image_num = 0 displayed_image_num = 0 tmp_selection_img_path_set = set() gallery_selected_image_path = '' selection_selected_image_path = '' show_only_selected_tags = True # ================================================================ # General Callbacks # ================================================================ CONFIG_PATH = os.path.join(scripts.basedir(), 'config.json') GeneralConfig = namedtuple('GeneralConfig', [ 'backup', 'dataset_dir', 'caption_ext', 'load_recursive', 'load_caption_from_filename', 'use_interrogator', 'use_interrogator_names', 'use_custom_threshold_booru', 'custom_threshold_booru', 'use_custom_threshold_waifu', 'custom_threshold_waifu', 'save_kohya_metadata', 'meta_output_path', 'meta_input_path', 'meta_overwrite', 'meta_save_as_caption', 'meta_use_full_path' ]) FilterConfig = namedtuple('FilterConfig', ['sw_prefix', 'sw_suffix', 'sw_regex','sort_by', 'sort_order', 'logic']) BatchEditConfig = namedtuple('BatchEditConfig', ['show_only_selected', 'prepend', 'use_regex', 'target', 'sw_prefix', 'sw_suffix', 'sw_regex', 'sory_by', 'sort_order']) EditSelectedConfig = namedtuple('EditSelectedConfig', ['auto_copy', 'warn_change_not_saved', 'use_interrogator_name']) MoveDeleteConfig = namedtuple('MoveDeleteConfig', ['range', 'target', 'caption_ext', 'destination']) CFG_GENERAL_DEFAULT = GeneralConfig(True, '', '.txt', False, True, 'No', [], False, 0.7, False, 0.5, False, '', '', True, False, False) CFG_FILTER_P_DEFAULT = FilterConfig(False, False, False, 'Alphabetical Order', 'Ascending', 'AND') CFG_FILTER_N_DEFAULT = FilterConfig(False, False, False, 'Alphabetical Order', 'Ascending', 'OR') CFG_BATCH_EDIT_DEFAULT = BatchEditConfig(True, False, False, 'Only Selected Tags', False, False, False, 'Alphabetical Order', 'Ascending') CFG_EDIT_SELECTED_DEFAULT = EditSelectedConfig(False, False, '') CFG_MOVE_DELETE_DEFAULT = MoveDeleteConfig('Selected One', [], '.txt', '') class Config: def __init__(self): self.config = dict() def load(self): if not os.path.exists(CONFIG_PATH) or not os.path.isfile(CONFIG_PATH): self.config = dict() return with open(CONFIG_PATH, 'r') as f: try: j = json.load(f) or dict() except: j = dict() self.config = j def save(self): with open(CONFIG_PATH, 'w') as f: f.write(json.dumps(self.config)) def read(self, name: str): return self.config.get(name) def write(self, cfg: dict, name: str): self.config[name] = cfg config = Config() def write_general_config(*args): cfg = GeneralConfig(*args) config.write(cfg._asdict(), 'general') def write_filter_config(*args): hlen = len(args) // 2 cfg_p = FilterConfig(*args[:hlen]) cfg_n = FilterConfig(*args[hlen:]) config.write({'positive':cfg_p._asdict(), 'negative':cfg_n._asdict()}, 'filter') def write_batch_edit_config(*args): cfg = BatchEditConfig(*args) config.write(cfg._asdict(), 'batch_edit') def write_edit_selected_config(*args): cfg = EditSelectedConfig(*args) config.write(cfg._asdict(), 'edit_selected') def write_move_delete_config(*args): cfg = MoveDeleteConfig(*args) config.write(cfg._asdict(), 'file_move_delete') def read_config(name: str, config_type: Type, default: NamedTuple, compat_func = None): d = config.read(name) cfg = default if d: if compat_func: d = compat_func(d) d = cfg._asdict() | d d = {k:v for k,v in d.items() if k in cfg._asdict().keys()} cfg = config_type(**d) return cfg def read_general_config(): # for compatibility generalcfg_intterogator_names = [ ('use_blip_to_prefill', 'BLIP'), ('use_git_to_prefill', 'GIT-large-COCO'), ('use_booru_to_prefill', 'DeepDanbooru'), ('use_waifu_to_prefill', 'wd-v1-4-vit-tagger') ] use_interrogator_names = [] def compat_func(d: Dict[str, Any]): if 'use_interrogator_names' in d.keys(): return d for cfg in generalcfg_intterogator_names: if d.get(cfg[0]): use_interrogator_names.append(cfg[1]) d['use_interrogator_names'] = use_interrogator_names return d return read_config('general', GeneralConfig, CFG_GENERAL_DEFAULT, compat_func) def read_filter_config(): d = config.read('filter') d_p = d.get('positive') if d else None d_n = d.get('negative') if d else None cfg_p = CFG_FILTER_P_DEFAULT cfg_n = CFG_FILTER_N_DEFAULT if d_p: d_p = cfg_p._asdict() | d_p d_p = {k:v for k,v in d_p.items() if k in cfg_p._asdict().keys()} cfg_p = FilterConfig(**d_p) if d_n: d_n = cfg_n._asdict() | d_n d_n = {k:v for k,v in d_n.items() if k in cfg_n._asdict().keys()} cfg_n = FilterConfig(**d_n) return cfg_p, cfg_n def read_batch_edit_config(): return read_config('batch_edit', BatchEditConfig, CFG_BATCH_EDIT_DEFAULT) def read_edit_selected_config(): return read_config('edit_selected', EditSelectedConfig, CFG_EDIT_SELECTED_DEFAULT) def read_move_delete_config(): return read_config('file_move_delete', MoveDeleteConfig, CFG_MOVE_DELETE_DEFAULT) # ================================================================ # Callbacks for "Filter and Edit Tags" tab # ================================================================ def get_current_gallery_txt(): return f""" Displayed Images : {displayed_image_num} / {total_image_num} total
Current Tag Filter : {tag_filter_ui.get_filter()} {' AND ' if tag_filter_ui.get_filter().tags and tag_filter_ui_neg.get_filter().tags else ''} {tag_filter_ui_neg.get_filter()}
Current Selection Filter : {len(path_filter.paths)} images
Selected Image : {gallery_selected_image_path} """ def get_current_txt_selection(): return f"""Selected Image : {selection_selected_image_path}""" def get_current_move_or_delete_target_num(target_data: str, idx: int): if target_data == 'Selected One': idx = int(idx) return f'Target dataset num: {1 if idx != -1 else 0}' elif target_data == 'All Displayed Ones': img_paths = dataset_tag_editor.get_filtered_imgpaths(filters=get_filters()) return f'Target dataset num: {len(img_paths)}' else: return f'Target dataset num: 0' def load_files_from_dir( dir: str, caption_file_ext: str, recursive: bool, load_caption_from_filename: bool, use_interrogator: str, use_interrogator_names: List[str], use_custom_threshold_booru: bool, custom_threshold_booru: float, use_custom_threshold_waifu: bool, custom_threshold_waifu: float ): global total_image_num, displayed_image_num, tmp_selection_img_path_set, gallery_selected_image_path, selection_selected_image_path, path_filter interrogate_method = dte.InterrogateMethod.NONE if use_interrogator == 'If Empty': interrogate_method = dte.InterrogateMethod.PREFILL elif use_interrogator == 'Overwrite': interrogate_method = dte.InterrogateMethod.OVERWRITE elif use_interrogator == 'Prepend': interrogate_method = dte.InterrogateMethod.PREPEND elif use_interrogator == 'Append': interrogate_method = dte.InterrogateMethod.APPEND threshold_booru = custom_threshold_booru if use_custom_threshold_booru else shared.opts.interrogate_deepbooru_score_threshold threshold_waifu = custom_threshold_waifu if use_custom_threshold_waifu else shared.opts.interrogate_deepbooru_score_threshold dataset_tag_editor.load_dataset(dir, caption_file_ext, recursive, load_caption_from_filename, interrogate_method, use_interrogator_names, threshold_booru, threshold_waifu) img_paths = dataset_tag_editor.get_filtered_imgpaths(filters=[]) img_indices = dataset_tag_editor.get_filtered_imgindices(filters=[]) path_filter = filters.PathFilter() total_image_num = displayed_image_num = len(dataset_tag_editor.get_img_path_set()) tmp_selection_img_path_set = set() gallery_selected_image_path = '' selection_selected_image_path = '' return [ img_paths, [], get_current_gallery_txt(), get_current_txt_selection() ] +\ [gr.CheckboxGroup.update(value=[str(i) for i in img_indices], choices=[str(i) for i in img_indices]), True] +\ clear_tag_filters() +\ [tag_select_ui_remove.cbg_tags_update()] def update_gallery(): global displayed_image_num img_indices = dataset_tag_editor.get_filtered_imgindices(filters=get_filters()) displayed_image_num = len(img_indices) return [ [str(i) for i in img_indices], 1, -1, -1, -1, get_current_gallery_txt() ] def update_filter_and_gallery(): return \ [tag_filter_ui.cbg_tags_update(), tag_filter_ui_neg.cbg_tags_update()] +\ update_gallery() +\ update_common_tags() +\ [', '.join(tag_filter_ui.filter.tags)] +\ [tag_select_ui_remove.cbg_tags_update()] def clear_tag_filters(): tag_filter_ui.clear_filter() tag_filter_ui_neg.clear_filter() return update_filter_and_gallery() def update_common_tags(): if show_only_selected_tags: tags = ', '.join([t for t in dataset_tag_editor.get_common_tags(filters=get_filters()) if t in tag_filter_ui.filter.tags]) else: tags = ', '.join(dataset_tag_editor.get_common_tags(filters=get_filters())) return [tags, tags] def save_all_changes(backup: bool, caption_ext: str, save_kohya_metadata:bool, metadata_output:str, metadata_input:str, metadata_overwrite:bool, metadata_as_caption:bool, metadata_use_fullpath:bool): if not metadata_input: metadata_input = None saved, total, dir = dataset_tag_editor.save_dataset(backup, caption_ext, save_kohya_metadata, metadata_output, metadata_input, metadata_overwrite, metadata_as_caption, metadata_use_fullpath) return f'Saved text files : {saved}/{total} under {dir}' if total > 0 else '' # ================================================================ # Callbacks for "Filter by Selection" tab # ================================================================ def arrange_selection_order(paths: List[str]): return sorted(paths) def selection_index_changed(idx: int): global tmp_selection_img_path_set, selection_selected_image_path idx = int(idx) img_paths = arrange_selection_order(tmp_selection_img_path_set) if idx < 0 or len(img_paths) <= idx: selection_selected_image_path = '' idx = -1 else: selection_selected_image_path = img_paths[idx] return [get_current_txt_selection(), idx] def add_image_selection(idx: int): global tmp_selection_img_path_set idx = int(idx) img_paths = dataset_tag_editor.get_filtered_imgpaths(filters=get_filters()) if idx < 0 or len(img_paths) <= idx: idx = -1 else: tmp_selection_img_path_set.add(img_paths[idx]) return [arrange_selection_order(tmp_selection_img_path_set), idx] def add_all_displayed_image_selection(): global tmp_selection_img_path_set img_paths = dataset_tag_editor.get_filtered_imgpaths(filters=get_filters()) tmp_selection_img_path_set |= set(img_paths) return arrange_selection_order(tmp_selection_img_path_set) def invert_image_selection(): global tmp_selection_img_path_set img_paths = dataset_tag_editor.get_img_path_set() tmp_selection_img_path_set = img_paths - tmp_selection_img_path_set return arrange_selection_order(tmp_selection_img_path_set) def remove_image_selection(idx: int): global tmp_selection_img_path_set, selection_selected_image_path idx = int(idx) img_paths = arrange_selection_order(tmp_selection_img_path_set) if 0 < idx and idx < len(img_paths): tmp_selection_img_path_set.remove(img_paths[idx]) selection_selected_image_path = '' idx = -1 return [ arrange_selection_order(tmp_selection_img_path_set), get_current_txt_selection(), idx ] def clear_image_selection(): global tmp_selection_img_path_set, selection_selected_image_path, path_filter tmp_selection_img_path_set.clear() selection_selected_image_path = '' path_filter = filters.PathFilter() return[ [], get_current_txt_selection(), -1 ] def apply_image_selection_filter(): global path_filter if len(tmp_selection_img_path_set) > 0: path_filter = filters.PathFilter(tmp_selection_img_path_set, filters.PathFilter.Mode.INCLUSIVE) else: path_filter = filters.PathFilter() return update_filter_and_gallery() # ================================================================ # Callbacks for "Edit Caption of Selected Image" tab # ================================================================ def gallery_index_changed(prev_idx: int, next_idx: int, edit_caption: str, copy_automatically: bool, warn_change_not_saved: bool): global gallery_selected_image_path prev_idx = int(prev_idx) next_idx = int(next_idx) img_paths = dataset_tag_editor.get_filtered_imgpaths(filters=get_filters()) prev_tags_txt = '' if 0 <= prev_idx and prev_idx < len(img_paths): prev_tags_txt = ', '.join(dataset_tag_editor.get_tags_by_image_path(gallery_selected_image_path)) else: prev_idx = -1 next_tags_txt = '' if 0 <= next_idx and next_idx < len(img_paths): gallery_selected_image_path = img_paths[next_idx] next_tags_txt = ', '.join(dataset_tag_editor.get_tags_by_image_path(gallery_selected_image_path)) else: gallery_selected_image_path = '' return\ [prev_idx if warn_change_not_saved and edit_caption != prev_tags_txt else -1] +\ [next_tags_txt, next_tags_txt if copy_automatically else edit_caption] +\ [edit_caption] +\ [get_current_gallery_txt()] def dialog_selected_save_caption_change(prev_idx: int, edit_caption: str): prev_idx = int(prev_idx) return change_selected_image_caption(edit_caption, prev_idx) def change_selected_image_caption(tags_text: str, idx: int): idx = int(idx) img_paths = dataset_tag_editor.get_filtered_imgpaths(filters=get_filters()) edited_tags = [t.strip() for t in tags_text.split(',')] edited_tags = [t for t in edited_tags if t] if 0 <= idx and idx < len(img_paths): dataset_tag_editor.set_tags_by_image_path(imgpath=img_paths[idx], tags=edited_tags) return update_filter_and_gallery() def interrogate_selected_image(interrogator_name: str, use_threshold_booru: bool, threshold_booru: float, use_threshold_waifu: bool, threshold_waifu: float): global gallery_selected_image_path threshold_booru = threshold_booru if use_threshold_booru else shared.opts.interrogate_deepbooru_score_threshold threshold_waifu = threshold_waifu if use_threshold_waifu else shared.opts.interrogate_deepbooru_score_threshold return dte.interrogate_image(gallery_selected_image_path, interrogator_name, threshold_booru, threshold_waifu) # ================================================================ # Callbacks for "Batch Edit Captions" tab # ================================================================ def apply_edit_tags(search_tags: str, replace_tags: str, prepend: bool): search_tags = [t.strip() for t in search_tags.split(',')] search_tags = [t for t in search_tags if t] replace_tags = [t.strip() for t in replace_tags.split(',')] replace_tags = [t for t in replace_tags if t] dataset_tag_editor.replace_tags(search_tags = search_tags, replace_tags = replace_tags, filters=get_filters(), prepend = prepend) tag_filter_ui.get_filter().tags = dataset_tag_editor.get_replaced_tagset(tag_filter_ui.get_filter().tags, search_tags, replace_tags) tag_filter_ui_neg.get_filter().tags = dataset_tag_editor.get_replaced_tagset(tag_filter_ui_neg.get_filter().tags, search_tags, replace_tags) return update_filter_and_gallery() def search_and_replace(search_text: str, replace_text: str, target_text: str, use_regex: bool): if target_text == 'Only Selected Tags': selected_tags = set(tag_filter_ui.selected_tags) dataset_tag_editor.search_and_replace_selected_tags(search_text = search_text, replace_text=replace_text, selected_tags=selected_tags, filters=get_filters(), use_regex=use_regex) tag_filter_ui.filter.tags = dataset_tag_editor.search_and_replace_tag_set(search_text, replace_text, tag_filter_ui.filter.tags, selected_tags, use_regex) tag_filter_ui_neg.filter.tags = dataset_tag_editor.search_and_replace_tag_set(search_text, replace_text, tag_filter_ui_neg.filter.tags, selected_tags, use_regex) elif target_text == 'Each Tags': dataset_tag_editor.search_and_replace_selected_tags(search_text = search_text, replace_text=replace_text, selected_tags=None, filters=get_filters(), use_regex=use_regex) tag_filter_ui.filter.tags = dataset_tag_editor.search_and_replace_tag_set(search_text, replace_text, tag_filter_ui.filter.tags, None, use_regex) tag_filter_ui_neg.filter.tags = dataset_tag_editor.search_and_replace_tag_set(search_text, replace_text, tag_filter_ui_neg.filter.tags, None, use_regex) elif target_text == 'Entire Caption': dataset_tag_editor.search_and_replace_caption(search_text=search_text, replace_text=replace_text, filters=get_filters(), use_regex=use_regex) tag_filter_ui.filter.tags = dataset_tag_editor.search_and_replace_tag_set(search_text, replace_text, tag_filter_ui.filter.tags, None, use_regex) tag_filter_ui_neg.filter.tags = dataset_tag_editor.search_and_replace_tag_set(search_text, replace_text, tag_filter_ui_neg.filter.tags, None, use_regex) return update_filter_and_gallery() def cb_show_only_tags_selected_changed(value: bool): global show_only_selected_tags show_only_selected_tags = value return update_common_tags() def remove_duplicated_tags(): dataset_tag_editor.remove_duplicated_tags(get_filters()) return update_filter_and_gallery() def remove_selected_tags(): dataset_tag_editor.remove_tags(tag_select_ui_remove.selected_tags, get_filters()) return update_filter_and_gallery() # ================================================================ # Callbacks for "Move or Delete Files" tab # ================================================================ def move_files(target_data: str, target_file: List[str], caption_ext: str, dest_dir: str, idx: int = -1): move_img = 'Image File' in target_file move_txt = 'Caption Text File' in target_file move_bak = 'Caption Backup File' in target_file if target_data == 'Selected One': idx = int(idx) img_paths = dataset_tag_editor.get_filtered_imgpaths(filters=get_filters()) if 0 <= idx and idx < len(img_paths): dataset_tag_editor.move_dataset_file(img_paths[idx], caption_ext, dest_dir, move_img, move_txt, move_bak) dataset_tag_editor.construct_tag_counts() elif target_data == 'All Displayed Ones': dataset_tag_editor.move_dataset(dest_dir, caption_ext, get_filters(), move_img, move_txt, move_bak) return update_filter_and_gallery() def delete_files(target_data: str, target_file: List[str], caption_ext: str, idx: int = -1): delete_img = 'Image File' in target_file delete_txt = 'Caption Text File' in target_file delete_bak = 'Caption Backup File' in target_file if target_data == 'Selected One': idx = int(idx) img_paths = dataset_tag_editor.get_filtered_imgpaths(filters=get_filters()) if 0 <= idx and idx < len(img_paths): dataset_tag_editor.delete_dataset_file(delete_img, caption_ext, delete_txt, delete_bak) dataset_tag_editor.construct_tag_counts() elif target_data == 'All Displayed Ones': dataset_tag_editor.delete_dataset(caption_ext, get_filters(), delete_img, delete_txt, delete_bak) return update_filter_and_gallery() # ================================================================ # Script Callbacks # ================================================================ def on_ui_tabs(): global tag_filter_ui, tag_filter_ui_neg, tag_select_ui_remove config.load() print('[tag-editor] Settings has been read from config.json') cfg_general = read_general_config() cfg_filter_p, cfg_filter_n = read_filter_config() cfg_batch_edit = read_batch_edit_config() cfg_edit_selected = read_edit_selected_config() cfg_file_move_delete = read_move_delete_config() tag_filter_ui = ui.TagFilterUI(dataset_tag_editor, tag_filter_mode=filters.TagFilter.Mode.INCLUSIVE) tag_filter_ui_neg = ui.TagFilterUI(dataset_tag_editor, tag_filter_mode=filters.TagFilter.Mode.EXCLUSIVE) tag_select_ui_remove = ui.TagSelectUI(dataset_tag_editor) with gr.Blocks(analytics_enabled=False) as dataset_tag_editor_interface: with gr.Row(visible=False): btn_hidden_set_index = gr.Button(elem_id="dataset_tag_editor_btn_hidden_set_index") nb_hidden_image_index = gr.Number(value=-1, label='hidden_idx_next') nb_hidden_image_index_prev = gr.Number(value=-1, label='hidden_idx_prev') nb_hidden_image_index_save_or_not = gr.Number(value=-1, label='hidden_s_or_n') btn_hidden_save_caption = gr.Button(elem_id="dataset_tag_editor_btn_hidden_save_caption") tb_hidden_edit_caption = gr.Textbox() cbg_hidden_dataset_filter = gr.CheckboxGroup(label='Dataset Filter') nb_hidden_dataset_filter_apply = gr.Number(label='Filter Apply', value=-1) gr.HTML(value=""" This extension works well with text captions in comma-separated style (such as the tags generated by DeepBooru interrogator). """) with gr.Column(variant='panel'): with gr.Row(): with gr.Column(scale=1): btn_save_all_changes = gr.Button(value='Save all changes', variant='primary') with gr.Column(scale=2): cb_backup = gr.Checkbox(value=cfg_general.backup, label='Backup original text file (original file will be renamed like filename.000, .001, .002, ...)', interactive=True) gr.HTML(value='Note: New text file will be created if you are using filename as captions.') with gr.Row(): cb_save_kohya_metadata = gr.Checkbox(value=cfg_general.save_kohya_metadata, label="Save kohya-ss's finetuning metadata json", interactive=True) with gr.Row(): with gr.Column(variant='panel', visible=cfg_general.save_kohya_metadata) as kohya_metadata: tb_metadata_output = gr.Textbox(label='json output path', placeholder='C:\\path\\to\\metadata.json',value=cfg_general.meta_output_path) tb_metadata_input = gr.Textbox(label='json input path (Optional)', placeholder='C:\\path\\to\\metadata.json',value=cfg_general.meta_input_path) with gr.Row(): cb_metadata_overwrite = gr.Checkbox(value=cfg_general.meta_overwrite, label="Overwrite if output file exists", interactive=True) cb_metadata_as_caption = gr.Checkbox(value=cfg_general.meta_save_as_caption, label="Save metadata as caption", interactive=True) cb_metadata_use_fullpath = gr.Checkbox(value=cfg_general.meta_use_full_path, label="Save metadata image key as fullpath", interactive=True) with gr.Row(visible=False): txt_result = gr.Textbox(label='Results', interactive=False) with gr.Accordion(label='Reload/Save Settings (config.json)', open=False): with gr.Row(): btn_reload_config_file = gr.Button(value='Reload settings') btn_save_setting_as_default = gr.Button(value='Save current settings') btn_restore_default = gr.Button(value='Restore settings to default') with gr.Row().style(equal_height=False): with gr.Column(): with gr.Column(variant='panel'): with gr.Row(): with gr.Column(scale=3): tb_img_directory = gr.Textbox(label='Dataset directory', placeholder='C:\\directory\\of\\datasets', value=cfg_general.dataset_dir) with gr.Column(scale=1, min_width=60): tb_caption_file_ext = gr.Textbox(label='Caption File Ext', placeholder='.txt (on Load and Save)', value=cfg_general.caption_ext) with gr.Column(scale=1, min_width=80): btn_load_datasets = gr.Button(value='Load') with gr.Accordion(label='Dataset Load Settings'): with gr.Row(): with gr.Column(): cb_load_recursive = gr.Checkbox(value=cfg_general.load_recursive, label='Load from subdirectories') cb_load_caption_from_filename = gr.Checkbox(value=cfg_general.load_caption_from_filename, label='Load caption from filename if no text file exists') with gr.Column(): rb_use_interrogator = gr.Radio(choices=['No', 'If Empty', 'Overwrite', 'Prepend', 'Append'], value=cfg_general.use_interrogator, label='Use Interrogator Caption') dd_intterogator_names = gr.Dropdown(label = 'Interrogators', choices=dte.INTERROGATOR_NAMES, value=cfg_general.use_interrogator_names, interactive=True, multiselect=True) with gr.Accordion(label='Interrogator Settings', open=False): with gr.Row(): cb_use_custom_threshold_booru = gr.Checkbox(value=cfg_general.use_custom_threshold_booru, label='Use Custom Threshold (Booru)', interactive=True) sl_custom_threshold_booru = gr.Slider(minimum=0, maximum=1, value=cfg_general.custom_threshold_booru, step=0.01, interactive=True, label='Booru Score Threshold') with gr.Row(): cb_use_custom_threshold_waifu = gr.Checkbox(value=cfg_general.use_custom_threshold_waifu, label='Use Custom Threshold (WDv1.4 Tagger)', interactive=True) sl_custom_threshold_waifu = gr.Slider(minimum=0, maximum=1, value=cfg_general.custom_threshold_waifu, step=0.01, interactive=True, label='WDv1.4 Tagger Score Threshold') gl_dataset_images = gr.Gallery(label='Dataset Images', elem_id="dataset_tag_editor_dataset_gallery").style(grid=opts.dataset_editor_image_columns) txt_gallery = gr.HTML(value=get_current_gallery_txt()) with gr.Tab(label='Filter by Tags'): with gr.Row(): btn_clear_tag_filters = gr.Button(value='Clear tag filters') btn_clear_all_filters = gr.Button(value='Clear ALL filters') with gr.Tab(label='Positive Filter'): with gr.Column(variant='panel'): gr.HTML(value='Search tags / Filter images by tags (INCLUSIVE)') logic_p = filters.TagFilter.Logic.OR if cfg_filter_p.logic=='OR' else filters.TagFilter.Logic.NONE if cfg_filter_p.logic=='NONE' else filters.TagFilter.Logic.AND tag_filter_ui.create_ui(get_filters, logic_p, cfg_filter_p.sort_by, cfg_filter_p.sort_order, cfg_filter_p.sw_prefix, cfg_filter_p.sw_suffix, cfg_filter_p.sw_regex) with gr.Tab(label='Negative Filter'): with gr.Column(variant='panel'): gr.HTML(value='Search tags / Filter images by tags (EXCLUSIVE)') logic_n = filters.TagFilter.Logic.AND if cfg_filter_n.logic=='AND' else filters.TagFilter.Logic.NONE if cfg_filter_n.logic=='NONE' else filters.TagFilter.Logic.OR tag_filter_ui_neg.create_ui(get_filters, logic_n, cfg_filter_n.sort_by, cfg_filter_n.sort_order, cfg_filter_n.sw_prefix, cfg_filter_n.sw_suffix, cfg_filter_n.sw_regex) with gr.Tab(label='Filter by Selection'): with gr.Row(visible=False): btn_hidden_set_selection_index = gr.Button(elem_id="dataset_tag_editor_btn_hidden_set_selection_index") nb_hidden_selection_image_index = gr.Number(value=-1) gr.HTML("""Select images from the left gallery.""") with gr.Column(variant='panel'): with gr.Row(): btn_add_image_selection = gr.Button(value='Add selection [Enter]', elem_id='dataset_tag_editor_btn_add_image_selection') btn_add_all_displayed_image_selection = gr.Button(value='Add ALL Displayed') gl_filter_images = gr.Gallery(label='Filter Images', elem_id="dataset_tag_editor_filter_gallery").style(grid=opts.dataset_editor_image_columns) txt_selection = gr.HTML(value=get_current_txt_selection()) with gr.Row(): btn_remove_image_selection = gr.Button(value='Remove selection [Delete]', elem_id='dataset_tag_editor_btn_remove_image_selection') btn_invert_image_selection = gr.Button(value='Invert selection') btn_clear_image_selection = gr.Button(value='Clear selection') btn_apply_image_selection_filter = gr.Button(value='Apply selection filter', variant='primary') with gr.Tab(label='Batch Edit Captions'): with gr.Tab(label='Search and Replace'): with gr.Column(variant='panel'): gr.HTML('Edit common tags.') cb_show_only_tags_selected = gr.Checkbox(value=cfg_batch_edit.show_only_selected, label='Show only the tags selected in the Positive Filter') tb_common_tags = gr.Textbox(label='Common Tags', interactive=False) tb_edit_tags = gr.Textbox(label='Edit Tags', interactive=True) cb_prepend_tags = gr.Checkbox(value=cfg_batch_edit.prepend, label='Prepend additional tags') btn_apply_edit_tags = gr.Button(value='Apply changes to filtered images', variant='primary') with gr.Accordion(label='Show description of how to edit tags', open=False): gr.HTML(value=""" 1. The tags common to all displayed images are shown in comma separated style.
2. When changes are applied, all tags in each displayed images are replaced.
3. If you change some tags into blank, they will be erased.
4. If you add some tags to the end, they will be added to the end/beginning of the text file.
5. Changes are not applied to the text files until the "Save all changes" button is pressed.
ex A.
 Original Text = "A, A, B, C" Common Tags = "B, A" Edit Tags = "X, Y"
 Result = "Y, Y, X, C" (B->X, A->Y)
ex B.
 Original Text = "A, B, C" Common Tags = "(nothing)" Edit Tags = "X, Y"
 Result = "A, B, C, X, Y" (add X and Y to the end (default))
 Result = "X, Y, A, B, C" (add X and Y to the beginning ("Prepend additional tags" checked))
ex C.
 Original Text = "A, B, C, D, E" Common Tags = "A, B, D" Edit Tags = ", X, "
 Result = "X, C, E" (A->"", B->X, D->"")
""") with gr.Column(variant='panel'): gr.HTML('Search and Replace for all images displayed.') tb_sr_search_tags = gr.Textbox(label='Search Text', interactive=True) tb_sr_replace_tags = gr.Textbox(label='Replace Text', interactive=True) cb_use_regex = gr.Checkbox(label='Use regex', value=cfg_batch_edit.use_regex) rb_sr_replace_target = gr.Radio(['Only Selected Tags', 'Each Tags', 'Entire Caption'], value=cfg_batch_edit.target, label='Search and Replace in', interactive=True) tb_sr_selected_tags = gr.Textbox(label='Selected Tags', interactive=False, lines=2) btn_apply_sr_tags = gr.Button(value='Search and Replace', variant='primary') with gr.Tab(label='Remove'): with gr.Column(variant='panel'): gr.HTML('Remove duplicate tags from the images displayed.') btn_remove_duplicate = gr.Button(value='Remove duplicate tags', variant='primary') with gr.Column(variant='panel'): gr.HTML('Remove selected tags from the images displayed.') btn_remove_selected = gr.Button(value='Remove selected tags', variant='primary') tag_select_ui_remove.create_ui(get_filters, cfg_batch_edit.sory_by, cfg_batch_edit.sort_order, cfg_batch_edit.sw_prefix, cfg_batch_edit.sw_suffix, cfg_batch_edit.sw_regex) with gr.Tab(label='Edit Caption of Selected Image'): with gr.Tab(label='Read Caption from Selected Image'): tb_caption_selected_image = gr.Textbox(label='Caption of Selected Image', interactive=True, lines=6) with gr.Row(): btn_copy_caption = gr.Button(value='Copy and Overwrite') btn_prepend_caption = gr.Button(value='Prepend') btn_append_caption = gr.Button(value='Append') with gr.Tab(label='Interrogate Selected Image'): with gr.Row(): dd_intterogator_names_si = gr.Dropdown(label = 'Interrogator', choices=dte.INTERROGATOR_NAMES, value=cfg_edit_selected.use_interrogator_name, interactive=True, multiselect=False) btn_interrogate_si = gr.Button(value='Interrogate') tb_interrogate_selected_image = gr.Textbox(label='Interrogate Result', interactive=True, lines=6) with gr.Row(): btn_copy_interrogate = gr.Button(value='Copy and Overwrite') btn_prepend_interrogate = gr.Button(value='Prepend') btn_append_interrogate = gr.Button(value='Append') cb_copy_caption_automatically = gr.Checkbox(value=cfg_edit_selected.auto_copy, label='Copy caption from selected images automatically') cb_ask_save_when_caption_changed = gr.Checkbox(value=cfg_edit_selected.warn_change_not_saved, label='Warn if changes in caption is not saved') tb_edit_caption_selected_image = gr.Textbox(label='Edit Caption', interactive=True, lines=6) btn_apply_changes_selected_image = gr.Button(value='Apply changes to selected image', variant='primary') gr.HTML("""Changes are not applied to the text files until the "Save all changes" button is pressed.""") with gr.Tab(label='Move or Delete Files'): gr.HTML(value='Note: Moved or deleted images will be unloaded.') rb_move_or_delete_target_data = gr.Radio(choices=['Selected One', 'All Displayed Ones'], value=cfg_file_move_delete.range, label='Move or Delete') cbg_move_or_delete_target_file = gr.CheckboxGroup(choices=['Image File', 'Caption Text File', 'Caption Backup File'], label='Target', value=cfg_file_move_delete.target) tb_move_or_delete_caption_ext = gr.Textbox(label='Caption File Ext', placeholder='txt', value=cfg_file_move_delete.caption_ext) ta_move_or_delete_target_dataset_num = gr.HTML(value='Target dataset num: 0') tb_move_or_delete_destination_dir = gr.Textbox(label='Destination Directory', value=cfg_file_move_delete.destination) btn_move_or_delete_move_files = gr.Button(value='Move File(s)', variant='primary') gr.HTML(value='Note: DELETE cannot be undone. The files will be deleted completely.') btn_move_or_delete_delete_files = gr.Button(value='DELETE File(s)', variant='primary') o_filter_and_gallery = \ [tag_filter_ui.cbg_tags, tag_filter_ui_neg.cbg_tags, cbg_hidden_dataset_filter, nb_hidden_dataset_filter_apply, nb_hidden_image_index, nb_hidden_image_index_prev, nb_hidden_image_index_save_or_not, txt_gallery] +\ [tb_common_tags, tb_edit_tags] +\ [tb_sr_selected_tags] +\ [tag_select_ui_remove.cbg_tags] #---------------------------------------------------------------- # General components_general = [ cb_backup, tb_img_directory, tb_caption_file_ext, cb_load_recursive, cb_load_caption_from_filename, rb_use_interrogator, dd_intterogator_names, cb_use_custom_threshold_booru, sl_custom_threshold_booru, cb_use_custom_threshold_waifu, sl_custom_threshold_waifu, cb_save_kohya_metadata, tb_metadata_output, tb_metadata_input, cb_metadata_overwrite, cb_metadata_as_caption, cb_metadata_use_fullpath ] components_filter = \ [tag_filter_ui.cb_prefix, tag_filter_ui.cb_suffix, tag_filter_ui.cb_regex, tag_filter_ui.rb_sort_by, tag_filter_ui.rb_sort_order, tag_filter_ui.rb_logic] +\ [tag_filter_ui_neg.cb_prefix, tag_filter_ui_neg.cb_suffix, tag_filter_ui_neg.cb_regex, tag_filter_ui_neg.rb_sort_by, tag_filter_ui_neg.rb_sort_order, tag_filter_ui_neg.rb_logic] components_batch_edit = [cb_show_only_tags_selected, cb_prepend_tags, cb_use_regex, rb_sr_replace_target, tag_select_ui_remove.cb_prefix, tag_select_ui_remove.cb_suffix, tag_select_ui_remove.cb_regex, tag_select_ui_remove.rb_sort_by, tag_select_ui_remove.rb_sort_order] components_edit_selected = [cb_copy_caption_automatically, cb_ask_save_when_caption_changed, dd_intterogator_names_si] components_move_delete = [rb_move_or_delete_target_data, cbg_move_or_delete_target_file, tb_move_or_delete_caption_ext, tb_move_or_delete_destination_dir] configurable_components = components_general + components_filter + components_batch_edit + components_edit_selected + components_move_delete def reload_config_file(): config.load() p, n = read_filter_config() print('[tag-editor] Reload config.json') return read_general_config() + p + n + read_batch_edit_config() + read_edit_selected_config() + read_move_delete_config() btn_reload_config_file.click( fn=reload_config_file, outputs=configurable_components ) def save_settings_callback(*a): p = 0 def inc(v): nonlocal p p += v return p write_general_config(*a[p:inc(len(components_general))]) write_filter_config(*a[p:inc(len(components_filter))]) write_batch_edit_config(*a[p:inc(len(components_batch_edit))]) write_edit_selected_config(*a[p:inc(len(components_edit_selected))]) write_move_delete_config(*a[p:]) config.save() print('[tag-editor] Current settings have been saved into config.json') btn_save_setting_as_default.click( fn=save_settings_callback, inputs=configurable_components ) def restore_default_settings(): write_general_config(*CFG_GENERAL_DEFAULT) write_filter_config(*CFG_FILTER_P_DEFAULT, *CFG_FILTER_N_DEFAULT) write_batch_edit_config(*CFG_BATCH_EDIT_DEFAULT) write_edit_selected_config(*CFG_EDIT_SELECTED_DEFAULT) write_move_delete_config(*CFG_MOVE_DELETE_DEFAULT) print('[tag-editor] Restore default settings') return CFG_GENERAL_DEFAULT + CFG_FILTER_P_DEFAULT + CFG_FILTER_N_DEFAULT + CFG_BATCH_EDIT_DEFAULT + CFG_EDIT_SELECTED_DEFAULT + CFG_MOVE_DELETE_DEFAULT btn_restore_default.click( fn=restore_default_settings, outputs=configurable_components ) btn_save_all_changes.click( fn=save_all_changes, inputs=[cb_backup, tb_caption_file_ext, cb_save_kohya_metadata, tb_metadata_output, tb_metadata_input, cb_metadata_overwrite, cb_metadata_as_caption, cb_metadata_use_fullpath], outputs=[txt_result] ) btn_load_datasets.click( fn=load_files_from_dir, inputs=[tb_img_directory, tb_caption_file_ext, cb_load_recursive, cb_load_caption_from_filename, rb_use_interrogator, dd_intterogator_names, cb_use_custom_threshold_booru, sl_custom_threshold_booru, cb_use_custom_threshold_waifu, sl_custom_threshold_waifu], outputs= [gl_dataset_images, gl_filter_images, txt_gallery, txt_selection] + [cbg_hidden_dataset_filter, nb_hidden_dataset_filter_apply] + o_filter_and_gallery ) btn_load_datasets.click( fn=lambda:['', '', '', -1, -1, -1], outputs=[tb_common_tags, tb_edit_tags, tb_caption_selected_image, nb_hidden_image_index, nb_hidden_image_index_prev, nb_hidden_image_index_save_or_not] ) cb_save_kohya_metadata.change( fn=lambda x:gr.update(visible=x), inputs=cb_save_kohya_metadata, outputs=kohya_metadata ) #---------------------------------------------------------------- # Filter by Tags tab tag_filter_ui.set_callbacks( on_filter_update=lambda a, b: update_gallery() + update_common_tags() + [', '.join(tag_filter_ui.filter.tags)] + [get_current_move_or_delete_target_num(a, b)] + [tag_select_ui_remove.cbg_tags_update()], inputs=[rb_move_or_delete_target_data, nb_hidden_image_index], outputs= [cbg_hidden_dataset_filter, nb_hidden_dataset_filter_apply, nb_hidden_image_index, nb_hidden_image_index_prev, nb_hidden_image_index_save_or_not, txt_gallery] + [tb_common_tags, tb_edit_tags] + [tb_sr_selected_tags] + [ta_move_or_delete_target_dataset_num]+ [tag_select_ui_remove.cbg_tags], _js='(...args) => {dataset_tag_editor_gl_dataset_images_close(); return args}' ) tag_filter_ui_neg.set_callbacks( on_filter_update=lambda a, b: update_gallery() + update_common_tags() + [get_current_move_or_delete_target_num(a, b)] + [tag_select_ui_remove.cbg_tags_update()], inputs=[rb_move_or_delete_target_data, nb_hidden_image_index], outputs= [cbg_hidden_dataset_filter, nb_hidden_dataset_filter_apply, nb_hidden_image_index, nb_hidden_image_index_prev, nb_hidden_image_index_save_or_not, txt_gallery] + [tb_common_tags, tb_edit_tags] + [ta_move_or_delete_target_dataset_num] + [tag_select_ui_remove.cbg_tags], _js='(...args) => {dataset_tag_editor_gl_dataset_images_close(); return args}' ) nb_hidden_dataset_filter_apply.change( fn=lambda a, b: [a, b], _js='(x, y) => [y>=0 ? dataset_tag_editor_gl_dataset_images_filter(x) : x, -1]', inputs=[cbg_hidden_dataset_filter, nb_hidden_dataset_filter_apply], outputs=[cbg_hidden_dataset_filter, nb_hidden_dataset_filter_apply] ) btn_clear_tag_filters.click( fn=clear_tag_filters, outputs=o_filter_and_gallery ) btn_clear_tag_filters.click( fn=get_current_move_or_delete_target_num, inputs=[rb_move_or_delete_target_data, nb_hidden_image_index], outputs=[ta_move_or_delete_target_dataset_num] ) btn_clear_all_filters.click( fn=clear_image_selection, outputs=[gl_filter_images, txt_selection, nb_hidden_selection_image_index] ) btn_clear_all_filters.click( fn=clear_tag_filters, outputs=o_filter_and_gallery ) btn_clear_all_filters.click( fn=get_current_move_or_delete_target_num, inputs=[rb_move_or_delete_target_data, nb_hidden_image_index], outputs=[ta_move_or_delete_target_dataset_num] ) #---------------------------------------------------------------- # Filter by Selection tab btn_hidden_set_selection_index.click( fn=selection_index_changed, _js="(x) => [dataset_tag_editor_gl_filter_images_selected_index()]", inputs=[nb_hidden_selection_image_index], outputs=[txt_selection, nb_hidden_selection_image_index] ) btn_add_image_selection.click( fn=add_image_selection, inputs=[nb_hidden_image_index], outputs=[gl_filter_images, nb_hidden_image_index] ) btn_add_all_displayed_image_selection.click( fn=add_all_displayed_image_selection, outputs=gl_filter_images ) btn_invert_image_selection.click( fn=invert_image_selection, outputs=gl_filter_images ) btn_remove_image_selection.click( fn=remove_image_selection, inputs=[nb_hidden_selection_image_index], outputs=[gl_filter_images, txt_selection, nb_hidden_selection_image_index] ) btn_clear_image_selection.click( fn=clear_image_selection, outputs=[gl_filter_images, txt_selection, nb_hidden_selection_image_index] ) btn_apply_image_selection_filter.click( fn=apply_image_selection_filter, outputs=o_filter_and_gallery ) btn_apply_image_selection_filter.click( fn=get_current_move_or_delete_target_num, inputs=[rb_move_or_delete_target_data, nb_hidden_image_index], outputs=[ta_move_or_delete_target_dataset_num] ) btn_apply_image_selection_filter.click( fn=None, _js='() => dataset_tag_editor_gl_dataset_images_close()' ) #---------------------------------------------------------------- # Batch Edit Captions tab btn_apply_edit_tags.click( fn=apply_edit_tags, inputs=[tb_common_tags, tb_edit_tags, cb_prepend_tags], outputs=o_filter_and_gallery ) btn_apply_edit_tags.click( fn=get_current_move_or_delete_target_num, inputs=[rb_move_or_delete_target_data, nb_hidden_image_index], outputs=[ta_move_or_delete_target_dataset_num] ) btn_apply_edit_tags.click( fn=None, _js='() => dataset_tag_editor_gl_dataset_images_close()' ) btn_apply_sr_tags.click( fn=search_and_replace, inputs=[tb_sr_search_tags, tb_sr_replace_tags, rb_sr_replace_target, cb_use_regex], outputs=o_filter_and_gallery ) btn_apply_sr_tags.click( fn=get_current_move_or_delete_target_num, inputs=[rb_move_or_delete_target_data, nb_hidden_image_index], outputs=[ta_move_or_delete_target_dataset_num] ) btn_apply_sr_tags.click( fn=None, _js='() => dataset_tag_editor_gl_dataset_images_close()' ) cb_show_only_tags_selected.change( fn=cb_show_only_tags_selected_changed, inputs=cb_show_only_tags_selected, outputs=[tb_common_tags, tb_edit_tags] ) btn_remove_duplicate.click( fn=remove_duplicated_tags, outputs=o_filter_and_gallery ) tag_select_ui_remove.set_callbacks() btn_remove_selected.click( fn=remove_selected_tags, outputs=o_filter_and_gallery ) #---------------------------------------------------------------- # Edit Caption of Selected Image tab btn_hidden_set_index.click( fn=lambda x, y:[x, y], _js="(x, y) => [dataset_tag_editor_gl_dataset_images_selected_index(), x]", inputs=[nb_hidden_image_index, nb_hidden_image_index_prev], outputs=[nb_hidden_image_index, nb_hidden_image_index_prev] ) nb_hidden_image_index.change( fn=gallery_index_changed, inputs=[nb_hidden_image_index_prev, nb_hidden_image_index, tb_edit_caption_selected_image, cb_copy_caption_automatically, cb_ask_save_when_caption_changed], outputs=[nb_hidden_image_index_save_or_not] + [tb_caption_selected_image, tb_edit_caption_selected_image] + [tb_hidden_edit_caption] + [txt_gallery] ) nb_hidden_image_index_save_or_not.change( fn=lambda a:None, _js='(a) => dataset_tag_editor_ask_save_change_or_not(a)', inputs=nb_hidden_image_index_save_or_not ) btn_hidden_save_caption.click( fn=lambda iprev, e, t, inext: dialog_selected_save_caption_change(iprev, e) + [get_current_move_or_delete_target_num(t, inext)], inputs=[nb_hidden_image_index_prev, tb_hidden_edit_caption] + [rb_move_or_delete_target_data, nb_hidden_image_index], outputs=o_filter_and_gallery + [ta_move_or_delete_target_dataset_num] ) btn_copy_caption.click( fn=lambda a:a, inputs=[tb_caption_selected_image], outputs=[tb_edit_caption_selected_image] ) btn_append_caption.click( fn=lambda a, b : b + (', ' if a and b else '') + a, inputs=[tb_caption_selected_image, tb_edit_caption_selected_image], outputs=[tb_edit_caption_selected_image] ) btn_prepend_caption.click( fn=lambda a, b : a + (', ' if a and b else '') + b, inputs=[tb_caption_selected_image, tb_edit_caption_selected_image], outputs=[tb_edit_caption_selected_image] ) btn_interrogate_si.click( fn=interrogate_selected_image, inputs=[dd_intterogator_names_si, cb_use_custom_threshold_booru, sl_custom_threshold_booru, cb_use_custom_threshold_waifu, sl_custom_threshold_waifu], outputs=[tb_interrogate_selected_image] ) btn_copy_interrogate.click( fn=lambda a:a, inputs=[tb_interrogate_selected_image], outputs=[tb_edit_caption_selected_image] ) btn_append_interrogate.click( fn=lambda a, b : b + (', ' if a and b else '') + a, inputs=[tb_interrogate_selected_image, tb_edit_caption_selected_image], outputs=[tb_edit_caption_selected_image] ) btn_prepend_interrogate.click( fn=lambda a, b : a + (', ' if a and b else '') + b, inputs=[tb_interrogate_selected_image, tb_edit_caption_selected_image], outputs=[tb_edit_caption_selected_image] ) btn_apply_changes_selected_image.click( fn=change_selected_image_caption, inputs=[tb_edit_caption_selected_image, nb_hidden_image_index], outputs=o_filter_and_gallery ) btn_apply_changes_selected_image.click( fn=lambda a:a, inputs=[tb_edit_caption_selected_image], outputs=[tb_caption_selected_image] ) btn_apply_changes_selected_image.click( fn=get_current_move_or_delete_target_num, inputs=[rb_move_or_delete_target_data, nb_hidden_image_index], outputs=[ta_move_or_delete_target_dataset_num] ) btn_apply_changes_selected_image.click( fn=None, _js='() => dataset_tag_editor_gl_dataset_images_close()' ) #---------------------------------------------------------------- # Move or Delete Files rb_move_or_delete_target_data.change( fn=get_current_move_or_delete_target_num, inputs=[rb_move_or_delete_target_data, nb_hidden_image_index], outputs=[ta_move_or_delete_target_dataset_num] ) btn_move_or_delete_move_files.click( fn=move_files, inputs=[rb_move_or_delete_target_data, cbg_move_or_delete_target_file, tb_move_or_delete_caption_ext, tb_move_or_delete_destination_dir, nb_hidden_image_index], outputs=o_filter_and_gallery ) btn_move_or_delete_move_files.click( fn=get_current_move_or_delete_target_num, inputs=[rb_move_or_delete_target_data, nb_hidden_image_index], outputs=[ta_move_or_delete_target_dataset_num] ) btn_move_or_delete_move_files.click( fn=None, _js='() => dataset_tag_editor_gl_dataset_images_close()' ) btn_move_or_delete_delete_files.click( fn=delete_files, inputs=[rb_move_or_delete_target_data, cbg_move_or_delete_target_file, tb_move_or_delete_caption_ext, nb_hidden_image_index], outputs=o_filter_and_gallery ) btn_move_or_delete_delete_files.click( fn=get_current_move_or_delete_target_num, inputs=[rb_move_or_delete_target_data, nb_hidden_image_index], outputs=[ta_move_or_delete_target_dataset_num] ) btn_move_or_delete_delete_files.click( fn=None, _js='() => dataset_tag_editor_gl_dataset_images_close()' ) return [(dataset_tag_editor_interface, "Dataset Tag Editor", "dataset_tag_editor_interface")] def on_ui_settings(): section = ('dataset-tag-editor', "Dataset Tag Editor") shared.opts.add_option("dataset_editor_image_columns", shared.OptionInfo(6, "Number of columns on image gallery", section=section)) script_callbacks.on_ui_settings(on_ui_settings) script_callbacks.on_ui_tabs(on_ui_tabs)