diff --git a/invokeai/app/services/model_install/model_install_default.py b/invokeai/app/services/model_install/model_install_default.py index 49d3cfdf7f9..1ab48eeea73 100644 --- a/invokeai/app/services/model_install/model_install_default.py +++ b/invokeai/app/services/model_install/model_install_default.py @@ -762,7 +762,9 @@ def _remote_files_from_source( except ValueError: pass - return [RemoteModelFile(url=source.url, path=Path("."), size=0)], None + return [ + RemoteModelFile(url=self._normalize_huggingface_blob_url(source.url), path=Path("."), size=0) + ], None raise Exception(f"No files associated with {source}") @@ -1488,3 +1490,15 @@ def get_fetcher_from_url(url: str) -> Type[ModelMetadataFetchBase]: if re.match(r"^https?://huggingface.co/[^/]+/[^/]+$", url.lower()): return HuggingFaceMetadataFetch raise ValueError(f"Unsupported model source: '{url}'") + + @staticmethod + def _normalize_huggingface_blob_url(url: AnyHttpUrl) -> Url: + """Convert Hugging Face file page URLs to direct download URLs.""" + return Url( + re.sub( + r"^(https?://huggingface\.co/[^/]+/[^/]+)/blob/([^?#]+)([?#].*)?$", + r"\1/resolve/\2\3", + str(url), + flags=re.IGNORECASE, + ) + ) diff --git a/tests/app/services/model_install/test_model_install.py b/tests/app/services/model_install/test_model_install.py index de001178adc..4cf4fc78ca1 100644 --- a/tests/app/services/model_install/test_model_install.py +++ b/tests/app/services/model_install/test_model_install.py @@ -321,6 +321,22 @@ def test_simple_download(mm2_installer: ModelInstallServiceBase, mm2_app_config: assert isinstance(bus.events[4], ModelInstallCompleteEvent) # install completed +def test_huggingface_blob_url_uses_resolve_download_url(mm2_installer: ModelInstallServiceBase) -> None: + source = URLModelSource( + url=Url("https://huggingface.co/h94/IP-Adapter/blob/main/sdxl_models/ip-adapter.safetensors") + ) + + assert isinstance(mm2_installer, ModelInstallService) + files, metadata = mm2_installer._remote_files_from_source(source) + + assert metadata is None + assert len(files) == 1 + assert ( + str(files[0].url) + == "https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter.safetensors" + ) + + @pytest.mark.timeout(timeout=10, method="thread") def test_huggingface_install(mm2_installer: ModelInstallServiceBase, mm2_app_config: InvokeAIAppConfig) -> None: source = URLModelSource(url=Url("https://huggingface.co/stabilityai/sdxl-turbo"))