From 6d4b1f753dcae2ad9d765bceba582081382171ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guigand?= Date: Thu, 27 Aug 2026 10:19:53 +0200 Subject: [PATCH] Show friendly names in the file dropdowns (screen reader accessibility) --- tabs/inference/inference.py | 51 +++++++++++++++++++++++++------- tabs/realtime/realtime.py | 50 ++++++++++++++++++++++++++------ tabs/train/train.py | 58 ++++++++++++++++++++++++++++--------- tabs/tts/tts.py | 11 ++++--- 4 files changed, 133 insertions(+), 37 deletions(-) diff --git a/tabs/inference/inference.py b/tabs/inference/inference.py index 975bd8b1..82ca1b5d 100644 --- a/tabs/inference/inference.py +++ b/tabs/inference/inference.py @@ -122,6 +122,30 @@ def get_files(type="model"): return [t[2] for t in sorted(best.values(), key=lambda x: x[1])] +def display_name(path): + """Dropdown label for a path: the file or folder name, without extension.""" + return os.path.splitext(os.path.basename(path))[0] + + +def path_choices(paths): + """ + Convert a list of paths into (label, value) dropdown choices so only the + file name is displayed and announced instead of the full relative path. + The order of `paths` is preserved, and duplicate names are disambiguated + by appending the parent folder name. + """ + names = [display_name(path) for path in paths] + counts = {} + for name in names: + counts[name] = counts.get(name, 0) + 1 + choices = [] + for name, path in zip(names, paths): + if counts[name] > 1: + name = f"{name} ({os.path.basename(os.path.dirname(path))})" + choices.append((name, path)) + return choices + + default_weight = next(iter(get_files("model")), None) audio_paths = [ @@ -242,9 +266,9 @@ def change_choices(model): ] return ( - {"choices": models_list, "__type__": "update"}, - {"choices": indexes_list, "__type__": "update"}, - {"choices": sorted(audio_paths), "__type__": "update"}, + {"choices": path_choices(models_list), "__type__": "update"}, + {"choices": path_choices(indexes_list), "__type__": "update"}, + {"choices": path_choices(sorted(audio_paths)), "__type__": "update"}, { "choices": ( sorted(speakers) @@ -484,7 +508,10 @@ def filter_dropdowns(filter_text): all_indexes = sorted(get_files("index")) filtered_models = [m for m in all_models if ft in m.lower()] filtered_indexes = [i for i in all_indexes if ft in i.lower()] - return (gr.update(choices=filtered_models), gr.update(choices=filtered_indexes)) + return ( + gr.update(choices=path_choices(filtered_models)), + gr.update(choices=path_choices(filtered_indexes)), + ) def update_filter_visibility(_): @@ -505,7 +532,9 @@ def inference_tab(): model_file = gr.Dropdown( label=i18n("Voice Model"), info=i18n("Select the voice model to use for the conversion."), - choices=sorted(get_files("model"), key=extract_model_and_epoch), + choices=path_choices( + sorted(get_files("model"), key=extract_model_and_epoch) + ), value=default_weight, interactive=True, allow_custom_value=True, @@ -521,7 +550,7 @@ def inference_tab(): index_file = gr.Dropdown( label=i18n("Index File"), info=i18n("Select the index file to use for the conversion."), - choices=sorted(get_files("index")), + choices=path_choices(sorted(get_files("index"))), value=match_index(default_weight), interactive=True, allow_custom_value=True, @@ -565,7 +594,7 @@ def inference_tab(): audio = gr.Dropdown( label=i18n("Select Audio"), info=i18n("Select the audio to convert."), - choices=sorted(audio_paths), + choices=path_choices(sorted(audio_paths)), value=audio_paths[0] if audio_paths else "", interactive=True, allow_custom_value=True, @@ -1139,7 +1168,7 @@ def inference_tab(): with gr.Row(): embedder_model_custom = gr.Dropdown( label=i18n("Select Custom Embedder"), - choices=refresh_embedders_folders(), + choices=path_choices(refresh_embedders_folders()), interactive=True, allow_custom_value=True, ) @@ -1794,7 +1823,7 @@ def enforce_terms_batch(terms_accepted, *args): with gr.Row(): embedder_model_custom_batch = gr.Dropdown( label=i18n("Select Custom Embedder"), - choices=refresh_embedders_folders(), + choices=path_choices(refresh_embedders_folders()), interactive=True, allow_custom_value=True, ) @@ -2194,7 +2223,7 @@ def delay_visible(checkbox): outputs=[], ) refresh_embedders_button.click( - fn=lambda: gr.update(choices=refresh_embedders_folders()), + fn=lambda: gr.update(choices=path_choices(refresh_embedders_folders())), inputs=[], outputs=[embedder_model_custom], ) @@ -2208,7 +2237,7 @@ def delay_visible(checkbox): outputs=[], ) refresh_embedders_button_batch.click( - fn=lambda: gr.update(choices=refresh_embedders_folders()), + fn=lambda: gr.update(choices=path_choices(refresh_embedders_folders())), inputs=[], outputs=[embedder_model_custom_batch], ) diff --git a/tabs/realtime/realtime.py b/tabs/realtime/realtime.py index 1e76daa7..9702c2aa 100644 --- a/tabs/realtime/realtime.py +++ b/tabs/realtime/realtime.py @@ -101,6 +101,30 @@ def get_files(type="model"): return [t[2] for t in sorted(best.values(), key=lambda x: x[1])] +def display_name(path): + """Dropdown label for a path: the file or folder name, without extension.""" + return os.path.splitext(os.path.basename(path))[0] + + +def path_choices(paths): + """ + Convert a list of paths into (label, value) dropdown choices so only the + file name is displayed and announced instead of the full relative path. + The order of `paths` is preserved, and duplicate names are disambiguated + by appending the parent folder name. + """ + names = [display_name(path) for path in paths] + counts = {} + for name in names: + counts[name] = counts.get(name, 0) + 1 + choices = [] + for name, path in zip(names, paths): + if counts[name] > 1: + name = f"{name} ({os.path.basename(os.path.dirname(path))})" + choices.append((name, path)) + return choices + + def folders_same( a: str, b: str ) -> bool: # Used to "pair" index and model folders based on path names @@ -1138,7 +1162,7 @@ def realtime_tab(): ) model_file = gr.Dropdown( label=i18n("Voice Model"), - choices=model_choices, + choices=path_choices(model_choices), interactive=True, value=get_safe_dropdown_value( saved_settings["model_file"], model_choices, default_weight @@ -1148,7 +1172,7 @@ def realtime_tab(): index_choices = sorted(get_files("index")) index_file = gr.Dropdown( label=i18n("Index File"), - choices=index_choices, + choices=path_choices(index_choices), value=get_safe_index_value( saved_settings["index_file"], index_choices, @@ -1604,7 +1628,7 @@ def realtime_tab(): with gr.Row(): embedder_model_custom = gr.Dropdown( label=i18n("Select Custom Embedder"), - choices=refresh_embedders_folders(), + choices=path_choices(refresh_embedders_folders()), interactive=True, allow_custom_value=True, ) @@ -1731,7 +1755,7 @@ def update_on_model_change(model_path): ) return gr.update( - choices=new_index_choices, value=safe_index_value + choices=path_choices(new_index_choices), value=safe_index_value ), gr.update(choices=new_sids, value=0 if new_sids else None) def refresh_devices(): @@ -1824,7 +1848,7 @@ def toggle_visible_embedder_custom(embedder_model): outputs=[], ) refresh_embedders_button.click( - fn=lambda: gr.update(choices=refresh_embedders_folders()), + fn=lambda: gr.update(choices=path_choices(refresh_embedders_folders())), inputs=[], outputs=[embedder_model_custom], ) @@ -2202,8 +2226,12 @@ def refresh_all(): new_names = get_files("model") new_indexes = sorted(get_files("index")) return ( - gr.update(choices=sorted(new_names, key=extract_model_and_epoch)), - gr.update(choices=new_indexes), + gr.update( + choices=path_choices( + sorted(new_names, key=extract_model_and_epoch) + ) + ), + gr.update(choices=path_choices(new_indexes)), ) refresh_button.click( @@ -2223,8 +2251,12 @@ def refresh_all(): output_choices.keys() ) return ( - gr.update(choices=sorted(new_names, key=extract_model_and_epoch)), - gr.update(choices=new_indexes), + gr.update( + choices=path_choices( + sorted(new_names, key=extract_model_and_epoch) + ) + ), + gr.update(choices=path_choices(new_indexes)), gr.update(choices=input_choices), gr.update(choices=output_choices), gr.update(choices=output_choices), diff --git a/tabs/train/train.py b/tabs/train/train.py index 6e582bf1..84cf55d0 100644 --- a/tabs/train/train.py +++ b/tabs/train/train.py @@ -70,8 +70,14 @@ def get_pretrained_list(suffix): def refresh_custom_pretraineds(): return ( - {"choices": sorted(get_pretrained_list("G")), "__type__": "update"}, - {"choices": sorted(get_pretrained_list("D")), "__type__": "update"}, + { + "choices": path_choices(sorted(get_pretrained_list("G"))), + "__type__": "update", + }, + { + "choices": path_choices(sorted(get_pretrained_list("D"))), + "__type__": "update", + }, ) @@ -93,7 +99,7 @@ def get_datasets_list(): def refresh_datasets(): - return {"choices": sorted(get_datasets_list()), "__type__": "update"} + return {"choices": path_choices(sorted(get_datasets_list())), "__type__": "update"} # Model Names @@ -117,10 +123,34 @@ def refresh_models(): def refresh_models_and_datasets(): return ( {"choices": sorted(get_models_list()), "__type__": "update"}, - {"choices": sorted(get_datasets_list()), "__type__": "update"}, + {"choices": path_choices(sorted(get_datasets_list())), "__type__": "update"}, ) +def display_name(path): + """Dropdown label for a path: the file or folder name, without extension.""" + return os.path.splitext(os.path.basename(path))[0] + + +def path_choices(paths): + """ + Convert a list of paths into (label, value) dropdown choices so only the + file name is displayed and announced instead of the full relative path. + The order of `paths` is preserved, and duplicate names are disambiguated + by appending the parent folder name. + """ + names = [display_name(path) for path in paths] + counts = {} + for name in names: + counts[name] = counts.get(name, 0) + 1 + choices = [] + for name, path in zip(names, paths): + if counts[name] > 1: + name = f"{name} ({os.path.basename(os.path.dirname(path))})" + choices.append((name, path)) + return choices + + # Refresh Custom Embedders def get_embedder_custom_list(): return [ @@ -242,8 +272,8 @@ def get_index_list(): def refresh_pth_and_index_list(): return ( - {"choices": sorted(get_pth_list()), "__type__": "update"}, - {"choices": sorted(get_index_list()), "__type__": "update"}, + {"choices": path_choices(sorted(get_pth_list())), "__type__": "update"}, + {"choices": path_choices(sorted(get_index_list())), "__type__": "update"}, ) @@ -410,7 +440,7 @@ def _extract_with_toast(*args): label=i18n("Dataset Path"), info=i18n("Path to the dataset folder."), # placeholder=i18n("Enter dataset path"), - choices=get_datasets_list(), + choices=path_choices(get_datasets_list()), allow_custom_value=True, interactive=True, ) @@ -587,7 +617,7 @@ def _extract_with_toast(*args): with gr.Row(): embedder_model_custom = gr.Dropdown( label=i18n("Select Custom Embedder"), - choices=refresh_embedders_folders(), + choices=path_choices(refresh_embedders_folders()), interactive=True, allow_custom_value=True, ) @@ -748,7 +778,7 @@ def _extract_with_toast(*args): info=i18n( "Select the custom pretrained model for the generator." ), - choices=sorted(pretraineds_list_g), + choices=path_choices(sorted(pretraineds_list_g)), interactive=True, allow_custom_value=True, ) @@ -757,7 +787,7 @@ def _extract_with_toast(*args): info=i18n( "Select the custom pretrained model for the discriminator." ), - choices=sorted(pretraineds_list_d), + choices=path_choices(sorted(pretraineds_list_d)), interactive=True, allow_custom_value=True, ) @@ -832,7 +862,7 @@ def enforce_terms(terms_accepted, *args): pth_dropdown_export = gr.Dropdown( label=i18n("Pth file"), info=i18n("Select the pth file to be exported"), - choices=get_pth_list(), + choices=path_choices(get_pth_list()), value=None, interactive=True, allow_custom_value=True, @@ -847,7 +877,7 @@ def enforce_terms(terms_accepted, *args): index_dropdown_export = gr.Dropdown( label=i18n("Index File"), info=i18n("Select the index file to be exported"), - choices=get_index_list(), + choices=path_choices(get_index_list()), value=None, interactive=True, allow_custom_value=True, @@ -977,7 +1007,9 @@ def update_slider_visibility(noise_reduction): outputs=[], ) refresh_embedders_button.click( - fn=refresh_embedders_folders, inputs=[], outputs=[embedder_model_custom] + fn=lambda: gr.update(choices=path_choices(refresh_embedders_folders())), + inputs=[], + outputs=[embedder_model_custom], ) pretrained.change( fn=toggle_pretrained, diff --git a/tabs/tts/tts.py b/tabs/tts/tts.py index b0aa4b86..331005ac 100644 --- a/tabs/tts/tts.py +++ b/tabs/tts/tts.py @@ -20,6 +20,7 @@ get_files, get_speakers_id, match_index, + path_choices, refresh_embedders_folders, update_filter_visibility, ) @@ -56,7 +57,9 @@ def tts_tab(): model_file = gr.Dropdown( label=i18n("Voice Model"), info=i18n("Select the voice model to use for the conversion."), - choices=sorted(get_files("model"), key=extract_model_and_epoch), + choices=path_choices( + sorted(get_files("model"), key=extract_model_and_epoch) + ), interactive=True, value=default_weight, allow_custom_value=True, @@ -73,7 +76,7 @@ def tts_tab(): index_file = gr.Dropdown( label=i18n("Index File"), info=i18n("Select the index file to use for the conversion."), - choices=sorted(get_files("index")), + choices=path_choices(sorted(get_files("index"))), value=match_index(default_weight), interactive=True, allow_custom_value=True, @@ -326,7 +329,7 @@ def tts_tab(): with gr.Row(): embedder_model_custom = gr.Dropdown( label=i18n("Select Custom Embedder"), - choices=refresh_embedders_folders(), + choices=path_choices(refresh_embedders_folders()), interactive=True, allow_custom_value=True, ) @@ -444,7 +447,7 @@ def toggle_visible_embedder_custom(embedder_model): outputs=[], ) refresh_embedders_button.click( - fn=lambda: gr.update(choices=refresh_embedders_folders()), + fn=lambda: gr.update(choices=path_choices(refresh_embedders_folders())), inputs=[], outputs=[embedder_model_custom], )