| import copy |
| import os |
| import re |
|
|
|
|
| METADATA_KEY = "metadata" |
|
|
|
|
| _IMAGE_PROMPT_LABELS = { |
| "": "Text/new generation", |
| "T": "Text/new generation", |
| "S": "Start image", |
| "E": "End image", |
| "V": "Continue from source video", |
| "L": "Continue from last generated video", |
| } |
|
|
|
|
| def get_model_family(base_model_type, model_def, model_types_handlers, families_infos, for_ui=False): |
| if for_ui: |
| model_family = model_def.get("group", None) |
| if model_family is not None and model_family in families_infos: |
| return model_family |
| handler = model_types_handlers.get(base_model_type, None) |
| if handler is None: |
| return "unknown" |
| return handler.query_model_family() |
|
|
|
|
| def _model_def_finetune(model_def): |
| return os.path.basename(os.path.dirname(str(model_def.get("path", "") or ""))).casefold() == "finetunes" |
|
|
|
|
| def _choice_values(choice_def): |
| if not isinstance(choice_def, dict): |
| return [] |
| values = [] |
| for choice in choice_def.get("choices", []) or []: |
| if isinstance(choice, (list, tuple)): |
| values.append(str(choice[1] if len(choice) > 1 else choice[0] if len(choice) > 0 else "") or "") |
| else: |
| values.append(str(choice or "")) |
| values.extend(str(value or "") for value in choice_def.get("selection", []) or []) |
| default = choice_def.get("default", None) |
| if default is not None: |
| values.append(str(default or "")) |
| return values |
|
|
|
|
| def _choice_values_contain(choice_def, letters): |
| return any(any(letter in value for letter in letters) for value in _choice_values(choice_def)) |
|
|
|
|
| def _normalized_choices(choice_def): |
| if not isinstance(choice_def, dict): |
| return [] |
| labels = choice_def.get("labels", {}) or {} |
| choices = [] |
| seen = set() |
|
|
| def add_choice(label, value): |
| value = str(value or "") |
| if value in seen: |
| return |
| seen.add(value) |
| choices.append({"label": str(label or value or "None"), "value": value}) |
|
|
| for choice in choice_def.get("choices", []) or []: |
| if isinstance(choice, (list, tuple)): |
| label = choice[0] if len(choice) > 0 else "" |
| value = choice[1] if len(choice) > 1 else label |
| add_choice(label, value) |
| else: |
| add_choice(labels.get(choice, choice), choice) |
| for key, label in labels.items(): |
| add_choice(label, key.replace("V", "").replace("P", "")) |
| for value in choice_def.get("selection", []) or []: |
| add_choice(labels.get(value, value), value) |
| return choices |
|
|
|
|
| def normalize_choice_def(choice_def): |
| if not isinstance(choice_def, dict): |
| return None |
| keys = ("default", "label", "letters_filter", "visible", "show_label", "scale", "trigger", "type") |
| normalized = {key: choice_def[key] for key in keys if key in choice_def} |
| normalized["choices"] = _normalized_choices(choice_def) |
| return normalized |
|
|
|
|
| def infer_main_outputs(model_def): |
| if model_def.get("audio_only", False): |
| return ["audio"] |
| if model_def.get("image_outputs", False): |
| return ["image"] |
| if model_def.get("v2i_switch_supported", False) or model_def.get("inpaint_support", False): |
| return ["image", "video"] |
| return ["video"] |
|
|
|
|
| def infer_outputs(model_def): |
| outputs = infer_main_outputs(model_def) |
| if model_def.get("returns_audio", False) and "audio" not in outputs: |
| outputs.append("audio") |
| return outputs |
|
|
|
|
| def infer_inputs(model_def): |
| inputs = ["text"] |
| image_prompt_types_allowed = str(model_def.get("image_prompt_types_allowed", "") or "") |
| image_refs = model_def.get("image_ref_choices", None) |
| alt_guide_refs = model_def.get("guide_custom_choices", None) |
| guide_preprocessing = model_def.get("guide_preprocessing", None) |
| custom_video_selection = model_def.get("custom_video_selection", None) |
| image_outputs = bool(model_def.get("image_outputs", False)) |
|
|
| if model_def.get("any_audio_prompt", False): |
| inputs.append("audio") |
| if "S" in image_prompt_types_allowed or "E" in image_prompt_types_allowed or model_def.get("end_frames_always_enabled", False) or model_def.get("inpaint_support", False) or _choice_values_contain(image_refs, "IKF") or _choice_values_contain(alt_guide_refs, "IKF"): |
| inputs.append("image") |
| if image_outputs and (_choice_values_contain(guide_preprocessing, "V") or _choice_values_contain(alt_guide_refs, "V")) and "image" not in inputs: |
| inputs.append("image") |
| if "V" in image_prompt_types_allowed or "L" in image_prompt_types_allowed or (not image_outputs and (_choice_values_contain(guide_preprocessing, "V") or _choice_values_contain(alt_guide_refs, "V") or _choice_values_contain(custom_video_selection, "V"))): |
| inputs.append("video") |
| return list(dict.fromkeys(inputs)) |
|
|
|
|
| def infer_media_inputs(model_def): |
| image_prompt_types_allowed = str(model_def.get("image_prompt_types_allowed", "") or "") |
| image_refs = model_def.get("image_ref_choices", None) |
| guide_preprocessing = model_def.get("guide_preprocessing", None) |
| guide_custom_choices = model_def.get("guide_custom_choices", None) |
| custom_video_selection = model_def.get("custom_video_selection", None) |
| has_reference = _choice_values_contain(image_refs, "I") or _choice_values_contain(guide_custom_choices, "I") |
| single_reference = bool(model_def.get("one_image_ref_needed", False) or model_def.get("one_image_ref_only", False)) |
| has_control = _choice_values_contain(guide_preprocessing, "V") or _choice_values_contain(guide_custom_choices, "V") or _choice_values_contain(custom_video_selection, "V") |
| image_outputs = bool(model_def.get("image_outputs", False)) |
| return { |
| "image": { |
| "start": "S" in image_prompt_types_allowed, |
| "end": "E" in image_prompt_types_allowed or bool(model_def.get("end_frames_always_enabled", False)), |
| "reference": has_reference, |
| "single_reference": has_reference and single_reference, |
| "multiple_references": has_reference and not single_reference, |
| "background": _choice_values_contain(image_refs, "K") or _choice_values_contain(guide_custom_choices, "K"), |
| "injected_frames": _choice_values_contain(image_refs, "F") or _choice_values_contain(guide_custom_choices, "F"), |
| "control": image_outputs and has_control, |
| "mask": image_outputs and (_choice_values_contain(model_def.get("mask_preprocessing", None), "A") or bool(model_def.get("inpaint_support", False))), |
| }, |
| "video": { |
| "continue": "V" in image_prompt_types_allowed, |
| "last": "L" in image_prompt_types_allowed, |
| "control": (not image_outputs) and has_control, |
| "mask": (not image_outputs) and _choice_values_contain(model_def.get("mask_preprocessing", None), "A"), |
| }, |
| "audio": { |
| "prompt": bool(model_def.get("any_audio_prompt", False)), |
| "output": bool(model_def.get("audio_only", False) or model_def.get("returns_audio", False)), |
| }, |
| } |
|
|
|
|
| def infer_capabilities(model_def, main_outputs, outputs, inputs, media_inputs): |
| image_inputs = media_inputs["image"] |
| video_inputs = media_inputs["video"] |
| audio_inputs = media_inputs["audio"] |
| return { |
| "text_to_video": "video" in main_outputs and "text" in inputs, |
| "image_to_video": "video" in main_outputs and image_inputs["start"], |
| "video_to_video": "video" in main_outputs and (video_inputs["continue"] or video_inputs["control"]), |
| "text_to_image": "image" in main_outputs and "text" in inputs, |
| "image_to_image": "image" in main_outputs and (image_inputs["start"] or image_inputs["reference"] or image_inputs["control"]), |
| "text_to_audio": "audio" in main_outputs and "text" in inputs, |
| "audio_to_audio": "audio" in main_outputs and audio_inputs["prompt"], |
| "audio_to_video": "video" in main_outputs and audio_inputs["prompt"], |
| "audio_output": "audio" in outputs, |
| "inpainting": bool(model_def.get("inpaint_support", False) or image_inputs["mask"] or video_inputs["mask"]), |
| "outpainting": bool(model_def.get("video_guide_outpainting", False)), |
| "reference_images": image_inputs["reference"], |
| "background_image": image_inputs["background"], |
| "injected_frames": image_inputs["injected_frames"], |
| "control_image": image_inputs["control"], |
| "control_video": video_inputs["control"], |
| "video_continuation": video_inputs["continue"], |
| "sliding_window": bool(model_def.get("sliding_window", False)), |
| "lora": not bool(model_def.get("no_lora", False)), |
| } |
|
|
|
|
| def infer_setting_values(model_def): |
| image_prompt_types_allowed = str(model_def.get("image_prompt_types_allowed", "") or "") |
| image_prompt_choices = [{"label": _IMAGE_PROMPT_LABELS.get("", ""), "value": ""}] |
| for letter in image_prompt_types_allowed: |
| if letter == "T": |
| continue |
| image_prompt_choices.append({"label": _IMAGE_PROMPT_LABELS.get(letter, letter), "value": letter}) |
| return { |
| "image_prompt_type": { |
| "allowed": image_prompt_types_allowed, |
| "choices": image_prompt_choices, |
| }, |
| "video_prompt_type": { |
| "guide_preprocessing": normalize_choice_def(model_def.get("guide_preprocessing", None)), |
| "mask_preprocessing": normalize_choice_def(model_def.get("mask_preprocessing", None)), |
| "guide_custom_choices": normalize_choice_def(model_def.get("guide_custom_choices", None)), |
| "image_ref_choices": normalize_choice_def(model_def.get("image_ref_choices", None)), |
| "custom_video_selection": normalize_choice_def(model_def.get("custom_video_selection", None)), |
| "forced": str(model_def.get("set_video_prompt_type", "") or ""), |
| }, |
| "audio_prompt_type": { |
| "sources": normalize_choice_def(model_def.get("audio_prompt_type_sources", None)), |
| "custom_option": model_def.get("audio_prompt_type_custom_option", None), |
| }, |
| "model_mode": normalize_choice_def(model_def.get("model_modes", None)), |
| "sample_solver": normalize_choice_def({"choices": model_def.get("sample_solvers", [])}) if model_def.get("sample_solvers", None) is not None else None, |
| "prompt_enhancer": normalize_choice_def(model_def.get("prompt_enhancer_def", None)), |
| } |
|
|
|
|
| def store_metadata(model_type, model_def, model_types_handlers, families_infos): |
| base_model_type = model_def.get("architecture", None) or model_type |
| family = get_model_family(base_model_type, model_def, model_types_handlers, families_infos, for_ui=True) |
| family_label = families_infos.get(family, families_infos.get("unknown", (100, "Unknown")))[1] |
| main_outputs = infer_main_outputs(model_def) |
| outputs = infer_outputs(model_def) |
| inputs = infer_inputs(model_def) |
| media_inputs = infer_media_inputs(model_def) |
| model_def[METADATA_KEY] = { |
| "model_type": model_type, |
| "family": family, |
| "family_label": family_label, |
| "base_model_type": base_model_type, |
| "finetune": _model_def_finetune(model_def), |
| "main_output": main_outputs, |
| "outputs": outputs, |
| "inputs": inputs, |
| "media_inputs": media_inputs, |
| "capabilities": infer_capabilities(model_def, main_outputs, outputs, inputs, media_inputs), |
| "setting_values": infer_setting_values(model_def), |
| } |
| return model_def |
|
|
|
|
| def _normalize_filter_values(value, *, split_string=True): |
| if value is None: |
| return None |
| if isinstance(value, (list, tuple, set)): |
| values = [str(one).strip() for one in value if str(one).strip()] |
| elif split_string and isinstance(value, str): |
| values = [one.strip() for one in re.split(r"[,|/]", value) if one.strip()] |
| else: |
| values = [str(value).strip()] if str(value).strip() else [] |
| return values or None |
|
|
|
|
| def _normalize_bool_filter(value): |
| if value is None or isinstance(value, str) and len(value.strip()) == 0: |
| return None |
| if isinstance(value, bool): |
| return value |
| text = str(value).strip().casefold() |
| if text in ("1", "true", "yes", "y", "on"): |
| return True |
| if text in ("0", "false", "no", "n", "off"): |
| return False |
| raise ValueError(f"Invalid finetune filter value: {value}") |
|
|
|
|
| def _metadata_matches_filter(value, expected): |
| expected = _normalize_filter_values(expected) |
| if expected is None: |
| return True |
| if isinstance(value, (list, tuple, set)): |
| actual = {str(one).casefold() for one in value} |
| return any(one.casefold() in actual for one in expected) |
| actual = str(value or "").casefold() |
| return any(actual == one.casefold() for one in expected) |
|
|
|
|
| def list_model_defs(models_def, *, family=None, base_model_type=None, finetune=None, model_type=None, main_output=None, inputs=None): |
| finetune_filter = _normalize_bool_filter(finetune) |
| requested_outputs = _normalize_filter_values(main_output) |
| requested_inputs = _normalize_filter_values(inputs) |
| requested_model_types = _normalize_filter_values(model_type, split_string=False) |
| records = [] |
| for one_model_type, model_def in models_def.items(): |
| metadata = model_def.get(METADATA_KEY, {}) |
| if requested_model_types is not None and one_model_type not in requested_model_types: |
| continue |
| if not _metadata_matches_filter(metadata.get("family"), family) and not _metadata_matches_filter(metadata.get("family_label"), family): |
| continue |
| if not _metadata_matches_filter(metadata.get("base_model_type"), base_model_type): |
| continue |
| if finetune_filter is not None and bool(metadata.get("finetune", False)) != finetune_filter: |
| continue |
| if requested_outputs is not None and not _metadata_matches_filter(metadata.get("main_output", []), requested_outputs): |
| continue |
| if requested_inputs is not None and not _metadata_matches_filter(metadata.get("inputs", []), requested_inputs): |
| continue |
| record = copy.deepcopy(model_def) |
| record["model_type"] = one_model_type |
| records.append(record) |
| return records |
|
|