Sanitize registry sync and clean shared model URL metadata
This commit is contained in:
+82
-82
@@ -4,7 +4,7 @@
|
||||
{
|
||||
"name": "Flux.1-dev-fp8.safetensors",
|
||||
"category": "Photorealism",
|
||||
"url": "https://huggingface.co/black-forest-labs/FLUX.1-dev",
|
||||
"url": "https://huggingface.co/black-forest-labs/FLUX.1-dev/resolve/main/Flux.1-dev-fp8.safetensors",
|
||||
"tags": [
|
||||
"flux",
|
||||
"realism",
|
||||
@@ -29,7 +29,7 @@
|
||||
{
|
||||
"name": "Flux.2-dev-fp16.safetensors",
|
||||
"category": "Photorealism",
|
||||
"url": "https://huggingface.co/black-forest-labs/FLUX.2-dev",
|
||||
"url": "https://huggingface.co/black-forest-labs/FLUX.2-dev/resolve/main/Flux.2-dev-fp16.safetensors",
|
||||
"tags": [
|
||||
"flux",
|
||||
"hyper-realism",
|
||||
@@ -97,7 +97,7 @@
|
||||
{
|
||||
"name": "Z-Image-Turbo-v1.safetensors",
|
||||
"category": "Photorealism",
|
||||
"url": "https://huggingface.co/Tongyi-MAI/Z-Image-Turbo",
|
||||
"url": "https://huggingface.co/Tongyi-MAI/Z-Image-Turbo/resolve/main/Z-Image-Turbo-v1.safetensors",
|
||||
"tags": [
|
||||
"photorealism",
|
||||
"architecture",
|
||||
@@ -165,7 +165,7 @@
|
||||
{
|
||||
"name": "IC-Light-XL-Realistic.safetensors",
|
||||
"category": "Photorealism",
|
||||
"url": "https://huggingface.co/lllyasviel/ic-light",
|
||||
"url": "https://huggingface.co/lllyasviel/ic-light/resolve/main/IC-Light-XL-Realistic.safetensors",
|
||||
"tags": [
|
||||
"photorealism",
|
||||
"relighting",
|
||||
@@ -253,7 +253,7 @@
|
||||
{
|
||||
"name": "Mobius_XL_v1.safetensors",
|
||||
"category": "Photorealism",
|
||||
"url": "https://civitai.com/models/mobius",
|
||||
"url": "https://civitai.com/models/17589",
|
||||
"tags": [
|
||||
"photorealism",
|
||||
"fantasy-realism",
|
||||
@@ -296,7 +296,7 @@
|
||||
{
|
||||
"name": "ThinkDiffusionXL_v1.safetensors",
|
||||
"category": "Photorealism",
|
||||
"url": "https://civitai.com/models/thinkdiffusion",
|
||||
"url": "https://civitai.com/models/926617",
|
||||
"tags": [
|
||||
"photorealism",
|
||||
"advertisement",
|
||||
@@ -317,7 +317,7 @@
|
||||
{
|
||||
"name": "NightVision_XL_v9.safetensors",
|
||||
"category": "Photorealism",
|
||||
"url": "https://civitai.com/models/nightvision",
|
||||
"url": "https://civitai.com/models/586010",
|
||||
"tags": [
|
||||
"photorealism",
|
||||
"night-photography",
|
||||
@@ -339,7 +339,7 @@
|
||||
{
|
||||
"name": "CyberRealistic_XL_v6.safetensors",
|
||||
"category": "Photorealism",
|
||||
"url": "https://civitai.com/models/cyberrealistic",
|
||||
"url": "https://civitai.com/models/1531982",
|
||||
"tags": [
|
||||
"photorealism",
|
||||
"portrait",
|
||||
@@ -361,7 +361,7 @@
|
||||
{
|
||||
"name": "Illustrious-XL-v1.0.safetensors",
|
||||
"category": "Anime",
|
||||
"url": "https://civitai.com/models/illustrious",
|
||||
"url": "https://civitai.com/models/2384857",
|
||||
"tags": [
|
||||
"anime",
|
||||
"illustration",
|
||||
@@ -405,7 +405,7 @@
|
||||
{
|
||||
"name": "AAM_XL_AnimeMix_v2.safetensors",
|
||||
"category": "Anime",
|
||||
"url": "https://civitai.com/models/anime-mix",
|
||||
"url": "https://civitai.com/models/269232",
|
||||
"tags": [
|
||||
"anime",
|
||||
"scenery",
|
||||
@@ -426,7 +426,7 @@
|
||||
{
|
||||
"name": "MeinaMix_V11.safetensors",
|
||||
"category": "Anime",
|
||||
"url": "https://civitai.com/models/meinamix",
|
||||
"url": "https://civitai.com/models/153831",
|
||||
"tags": [
|
||||
"anime",
|
||||
"soft-shading",
|
||||
@@ -447,7 +447,7 @@
|
||||
{
|
||||
"name": "Cetus-Mix-Final.safetensors",
|
||||
"category": "Anime",
|
||||
"url": "https://civitai.com/models/cetus",
|
||||
"url": "https://civitai.com/models/1947515",
|
||||
"tags": [
|
||||
"anime",
|
||||
"semi-realistic",
|
||||
@@ -468,7 +468,7 @@
|
||||
{
|
||||
"name": "NoobAI-XL-v1.safetensors",
|
||||
"category": "Anime",
|
||||
"url": "https://civitai.com/models/noobai",
|
||||
"url": "https://civitai.com/models/2339056",
|
||||
"tags": [
|
||||
"anime",
|
||||
"tag-adherence",
|
||||
@@ -489,7 +489,7 @@
|
||||
{
|
||||
"name": "Animagine_XL_v4.0.safetensors",
|
||||
"category": "Anime",
|
||||
"url": "https://civitai.com/models/animagine",
|
||||
"url": "https://civitai.com/models/1564077",
|
||||
"tags": [
|
||||
"anime",
|
||||
"official-art-style",
|
||||
@@ -510,7 +510,7 @@
|
||||
{
|
||||
"name": "AbyssOrangeMix3_AOM3.safetensors",
|
||||
"category": "Anime",
|
||||
"url": "https://civitai.com/models/abyssorangemix",
|
||||
"url": "https://civitai.com/models/21503",
|
||||
"tags": [
|
||||
"anime",
|
||||
"dark-fantasy",
|
||||
@@ -531,7 +531,7 @@
|
||||
{
|
||||
"name": "Counterfeit-V3.0.safetensors",
|
||||
"category": "Anime",
|
||||
"url": "https://civitai.com/models/counterfeit",
|
||||
"url": "https://civitai.com/models/4468",
|
||||
"tags": [
|
||||
"anime",
|
||||
"landscape-anime",
|
||||
@@ -552,7 +552,7 @@
|
||||
{
|
||||
"name": "BlueberryMix_XL.safetensors",
|
||||
"category": "Anime",
|
||||
"url": "https://civitai.com/models/blueberry",
|
||||
"url": "https://civitai.com/models/329642",
|
||||
"tags": [
|
||||
"anime",
|
||||
"cute-style",
|
||||
@@ -573,7 +573,7 @@
|
||||
{
|
||||
"name": "Mistoon_Anime_v2.safetensors",
|
||||
"category": "Anime",
|
||||
"url": "https://civitai.com/models/mistoon",
|
||||
"url": "https://civitai.com/models/32022",
|
||||
"tags": [
|
||||
"anime",
|
||||
"flat-color",
|
||||
@@ -651,7 +651,7 @@
|
||||
{
|
||||
"name": "Waifu-Diffusion-XL.safetensors",
|
||||
"category": "Anime",
|
||||
"url": "https://huggingface.co/waifu-diffusion",
|
||||
"url": "https://huggingface.co/waifu-diffusion/resolve/main/Waifu-Diffusion-XL.safetensors",
|
||||
"tags": [
|
||||
"base-anime",
|
||||
"massive-dataset",
|
||||
@@ -670,7 +670,7 @@
|
||||
{
|
||||
"name": "Painterly_XL_Check_v2.safetensors",
|
||||
"category": "Artistic",
|
||||
"url": "https://civitai.com/models/painterly",
|
||||
"url": "https://civitai.com/models/2326354",
|
||||
"tags": [
|
||||
"oil-painting",
|
||||
"canvas",
|
||||
@@ -710,7 +710,7 @@
|
||||
{
|
||||
"name": "Watercolor_Stylized_v3.safetensors",
|
||||
"category": "Artistic",
|
||||
"url": "https://civitai.com/models/watercolor",
|
||||
"url": "https://civitai.com/models/2289270",
|
||||
"tags": [
|
||||
"water-wash",
|
||||
"ink-bleed",
|
||||
@@ -729,7 +729,7 @@
|
||||
{
|
||||
"name": "Copax_TimeLess_XL_v9.safetensors",
|
||||
"category": "Artistic",
|
||||
"url": "https://civitai.com/models/timeless",
|
||||
"url": "https://civitai.com/models/118111",
|
||||
"tags": [
|
||||
"atmospheric",
|
||||
"ethereal",
|
||||
@@ -748,7 +748,7 @@
|
||||
{
|
||||
"name": "Oil_Brush_Pro_v2.safetensors",
|
||||
"category": "Artistic",
|
||||
"url": "https://civitai.com/models/oilbrush",
|
||||
"url": "https://civitai.com/models/118223",
|
||||
"tags": [
|
||||
"thick-paint",
|
||||
"museum-quality",
|
||||
@@ -767,7 +767,7 @@
|
||||
{
|
||||
"name": "Arcane_Style_XL.safetensors",
|
||||
"category": "Artistic",
|
||||
"url": "https://civitai.com/models/arcane",
|
||||
"url": "https://civitai.com/models/2183167",
|
||||
"tags": [
|
||||
"stylized",
|
||||
"neon",
|
||||
@@ -787,7 +787,7 @@
|
||||
{
|
||||
"name": "Inkpunk_Diffusion_v2.safetensors",
|
||||
"category": "Artistic",
|
||||
"url": "https://civitai.com/models/inkpunk",
|
||||
"url": "https://civitai.com/models/2124568",
|
||||
"tags": [
|
||||
"ink-sketch",
|
||||
"messy-lines",
|
||||
@@ -806,7 +806,7 @@
|
||||
{
|
||||
"name": "Abstract_Master_XL.safetensors",
|
||||
"category": "Artistic",
|
||||
"url": "https://civitai.com/models/abstract",
|
||||
"url": "https://civitai.com/models/2149089",
|
||||
"tags": [
|
||||
"geometric",
|
||||
"non-representative",
|
||||
@@ -825,7 +825,7 @@
|
||||
{
|
||||
"name": "Charcoal_Pro_v1.safetensors",
|
||||
"category": "Artistic",
|
||||
"url": "https://civitai.com/models/charcoal",
|
||||
"url": "https://civitai.com/models/2100594",
|
||||
"tags": [
|
||||
"black-and-white",
|
||||
"hand-drawn",
|
||||
@@ -844,7 +844,7 @@
|
||||
{
|
||||
"name": "Ukiyo-e_XL_v2.safetensors",
|
||||
"category": "Artistic",
|
||||
"url": "https://civitai.com/models/ukiyoe",
|
||||
"url": "https://civitai.com/models/1018901",
|
||||
"tags": [
|
||||
"japanese-print",
|
||||
"woodblock",
|
||||
@@ -863,7 +863,7 @@
|
||||
{
|
||||
"name": "ComicMix_XL_v3.safetensors",
|
||||
"category": "Artistic",
|
||||
"url": "https://civitai.com/models/comicmix",
|
||||
"url": "https://civitai.com/models/631262",
|
||||
"tags": [
|
||||
"western-comic",
|
||||
"marvel-style",
|
||||
@@ -882,7 +882,7 @@
|
||||
{
|
||||
"name": "Gouache_Style_XL.safetensors",
|
||||
"category": "Artistic",
|
||||
"url": "https://civitai.com/models/gouache",
|
||||
"url": "https://civitai.com/models/330040",
|
||||
"tags": [
|
||||
"matte-paint",
|
||||
"thick-wash",
|
||||
@@ -920,7 +920,7 @@
|
||||
{
|
||||
"name": "Synthwave_Master_XL.safetensors",
|
||||
"category": "Artistic",
|
||||
"url": "https://civitai.com/models/synthwave",
|
||||
"url": "https://civitai.com/models/1463207",
|
||||
"tags": [
|
||||
"80s-aesthetic",
|
||||
"retro-future",
|
||||
@@ -958,7 +958,7 @@
|
||||
{
|
||||
"name": "Modern_Architecture_XL.safetensors",
|
||||
"category": "Architecture",
|
||||
"url": "https://civitai.com/models/arch",
|
||||
"url": "https://civitai.com/models/2380151",
|
||||
"tags": [
|
||||
"blueprints",
|
||||
"glass",
|
||||
@@ -978,7 +978,7 @@
|
||||
{
|
||||
"name": "Interior_Design_Flux_v2.safetensors",
|
||||
"category": "Architecture",
|
||||
"url": "https://civitai.com/models/interior",
|
||||
"url": "https://civitai.com/models/2373956",
|
||||
"tags": [
|
||||
"home-staging",
|
||||
"photorealistic-rooms",
|
||||
@@ -997,7 +997,7 @@
|
||||
{
|
||||
"name": "Abandoned_Places_XL.safetensors",
|
||||
"category": "Architecture",
|
||||
"url": "https://civitai.com/models/overgrown",
|
||||
"url": "https://civitai.com/models/304561",
|
||||
"tags": [
|
||||
"ruins",
|
||||
"nature-reclaimed",
|
||||
@@ -1017,7 +1017,7 @@
|
||||
{
|
||||
"name": "CyberCity_XL_Pro.safetensors",
|
||||
"category": "Architecture",
|
||||
"url": "https://civitai.com/models/cybercity",
|
||||
"url": "https://civitai.com/models/1731924",
|
||||
"tags": [
|
||||
"skyscrapers",
|
||||
"neon",
|
||||
@@ -1036,7 +1036,7 @@
|
||||
{
|
||||
"name": "Brutalist_Logic_XL.safetensors",
|
||||
"category": "Architecture",
|
||||
"url": "https://civitai.com/models/brutalist",
|
||||
"url": "https://civitai.com/models/1636847",
|
||||
"tags": [
|
||||
"concrete",
|
||||
"geometric",
|
||||
@@ -1055,7 +1055,7 @@
|
||||
{
|
||||
"name": "Gothic_Stone_Carving.safetensors",
|
||||
"category": "Architecture",
|
||||
"url": "https://civitai.com/models/gothic",
|
||||
"url": "https://civitai.com/models/2268702",
|
||||
"tags": [
|
||||
"cathedral",
|
||||
"intricate-stone",
|
||||
@@ -1093,7 +1093,7 @@
|
||||
{
|
||||
"name": "Steampunk_City_v2.safetensors",
|
||||
"category": "Architecture",
|
||||
"url": "https://civitai.com/models/steamcity",
|
||||
"url": "https://civitai.com/models/2357299",
|
||||
"tags": [
|
||||
"brass",
|
||||
"pipes",
|
||||
@@ -1113,7 +1113,7 @@
|
||||
{
|
||||
"name": "Skyscraper_High_Angle.safetensors",
|
||||
"category": "Architecture",
|
||||
"url": "https://civitai.com/models/skyscraper",
|
||||
"url": "https://civitai.com/models/1170746",
|
||||
"tags": [
|
||||
"aerial-view",
|
||||
"glass-reflections",
|
||||
@@ -1170,7 +1170,7 @@
|
||||
{
|
||||
"name": "GrandCanyon_RedRock_XL.safetensors",
|
||||
"category": "Landscape",
|
||||
"url": "https://civitai.com/models/canyon",
|
||||
"url": "https://civitai.com/models/901560",
|
||||
"tags": [
|
||||
"desert-textures",
|
||||
"utah-scenery",
|
||||
@@ -1189,7 +1189,7 @@
|
||||
{
|
||||
"name": "Misty_Forest_XL_v3.safetensors",
|
||||
"category": "Landscape",
|
||||
"url": "https://civitai.com/models/misty",
|
||||
"url": "https://civitai.com/models/1447721",
|
||||
"tags": [
|
||||
"atmospheric-fog",
|
||||
"pine-trees",
|
||||
@@ -1208,7 +1208,7 @@
|
||||
{
|
||||
"name": "Arctic_Tundra_Pro.safetensors",
|
||||
"category": "Landscape",
|
||||
"url": "https://civitai.com/models/arctic",
|
||||
"url": "https://civitai.com/models/1632372",
|
||||
"tags": [
|
||||
"snow-ice",
|
||||
"blue-hour",
|
||||
@@ -1246,7 +1246,7 @@
|
||||
{
|
||||
"name": "Volcano_Fury_XL.safetensors",
|
||||
"category": "Landscape",
|
||||
"url": "https://civitai.com/models/volcano",
|
||||
"url": "https://civitai.com/models/160763",
|
||||
"tags": [
|
||||
"lava",
|
||||
"volcanic-smoke",
|
||||
@@ -1265,7 +1265,7 @@
|
||||
{
|
||||
"name": "Alpine_Valley_v2.safetensors",
|
||||
"category": "Landscape",
|
||||
"url": "https://civitai.com/models/alpine",
|
||||
"url": "https://civitai.com/models/852086",
|
||||
"tags": [
|
||||
"mountains",
|
||||
"green-valley",
|
||||
@@ -1284,7 +1284,7 @@
|
||||
{
|
||||
"name": "Lavender_Sunset_XL.safetensors",
|
||||
"category": "Landscape",
|
||||
"url": "https://civitai.com/models/lavender",
|
||||
"url": "https://civitai.com/models/2268947",
|
||||
"tags": [
|
||||
"floral-fields",
|
||||
"soft-sunset",
|
||||
@@ -1303,7 +1303,7 @@
|
||||
{
|
||||
"name": "Ocean_Deep_Pro.safetensors",
|
||||
"category": "Landscape",
|
||||
"url": "https://civitai.com/models/ocean",
|
||||
"url": "https://civitai.com/models/2385281",
|
||||
"tags": [
|
||||
"underwater",
|
||||
"coral-reefs",
|
||||
@@ -1322,7 +1322,7 @@
|
||||
{
|
||||
"name": "Martian_Colony_XL.safetensors",
|
||||
"category": "Landscape",
|
||||
"url": "https://civitai.com/models/mars",
|
||||
"url": "https://civitai.com/models/2242763",
|
||||
"tags": [
|
||||
"red-dust",
|
||||
"alien-sky",
|
||||
@@ -1341,7 +1341,7 @@
|
||||
{
|
||||
"name": "Wan_2.1_T2V_14B.safetensors",
|
||||
"category": "Video",
|
||||
"url": "https://huggingface.co/Wan-AI/Wan2.1-T2V-14B",
|
||||
"url": "https://huggingface.co/Wan-AI/Wan2.1-T2V-14B/resolve/main/Wan_2.1_T2V_14B.safetensors",
|
||||
"tags": [
|
||||
"text-to-video",
|
||||
"cinematic-motion",
|
||||
@@ -1359,7 +1359,7 @@
|
||||
{
|
||||
"name": "Wan2.1-t2v-1.3B.safetensors",
|
||||
"category": "Video",
|
||||
"url": "https://huggingface.co/Wan-AI/Wan2.1-T2V-1.3B",
|
||||
"url": "https://huggingface.co/Wan-AI/Wan2.1-T2V-1.3B/resolve/main/Wan2.1-t2v-1.3B.safetensors",
|
||||
"tags": [
|
||||
"text-to-video",
|
||||
"efficient",
|
||||
@@ -1377,7 +1377,7 @@
|
||||
{
|
||||
"name": "HunyuanVideo_v1.safetensors",
|
||||
"category": "Video",
|
||||
"url": "https://huggingface.co/tencent/HunyuanVideo",
|
||||
"url": "https://huggingface.co/tencent/HunyuanVideo/resolve/main/HunyuanVideo_v1.safetensors",
|
||||
"tags": [
|
||||
"ai-video",
|
||||
"long-duration",
|
||||
@@ -1395,7 +1395,7 @@
|
||||
{
|
||||
"name": "SVD-XT-1.1.safetensors",
|
||||
"category": "Video",
|
||||
"url": "https://huggingface.co/stabilityai/stable-video-diffusion",
|
||||
"url": "https://huggingface.co/stabilityai/stable-video-diffusion/resolve/main/SVD-XT-1.1.safetensors",
|
||||
"tags": [
|
||||
"image-to-video",
|
||||
"professional-motion"
|
||||
@@ -1409,7 +1409,7 @@
|
||||
{
|
||||
"name": "LivePortrait_XL_v2.safetensors",
|
||||
"category": "Video",
|
||||
"url": "https://civitai.com/models/liveportrait",
|
||||
"url": "https://civitai.com/models/736694",
|
||||
"tags": [
|
||||
"facial-animation",
|
||||
"vlog-automation",
|
||||
@@ -1447,7 +1447,7 @@
|
||||
{
|
||||
"name": "Blender-Style-XL.safetensors",
|
||||
"category": "3D Render",
|
||||
"url": "https://civitai.com/models/blender",
|
||||
"url": "https://civitai.com/models/1448527",
|
||||
"tags": [
|
||||
"cycles-render",
|
||||
"hard-surface",
|
||||
@@ -1466,7 +1466,7 @@
|
||||
{
|
||||
"name": "Clay-Aardman-Style.safetensors",
|
||||
"category": "3D Render",
|
||||
"url": "https://civitai.com/models/clay",
|
||||
"url": "https://civitai.com/models/2171802",
|
||||
"tags": [
|
||||
"stop-motion",
|
||||
"fingerprint-texture",
|
||||
@@ -1485,7 +1485,7 @@
|
||||
{
|
||||
"name": "Unreal-Engine-5-XL.safetensors",
|
||||
"category": "3D Render",
|
||||
"url": "https://civitai.com/models/ue5",
|
||||
"url": "https://civitai.com/models/111551",
|
||||
"tags": [
|
||||
"game-engine-lighting",
|
||||
"high-poly",
|
||||
@@ -1504,7 +1504,7 @@
|
||||
{
|
||||
"name": "ZBrush-Sculpt-XL.safetensors",
|
||||
"category": "3D Render",
|
||||
"url": "https://civitai.com/models/zbrush",
|
||||
"url": "https://civitai.com/models/938778",
|
||||
"tags": [
|
||||
"digital-sculpt",
|
||||
"grey-clay",
|
||||
@@ -1523,7 +1523,7 @@
|
||||
{
|
||||
"name": "ControlNet-Union-SDXL.safetensors",
|
||||
"category": "Utility",
|
||||
"url": "https://huggingface.co/xinsir/controlnet-union",
|
||||
"url": "https://huggingface.co/xinsir/controlnet-union/resolve/main/ControlNet-Union-SDXL.safetensors",
|
||||
"tags": [
|
||||
"depth",
|
||||
"canny",
|
||||
@@ -1543,7 +1543,7 @@
|
||||
{
|
||||
"name": "IP-Adapter-FaceID-XL.safetensors",
|
||||
"category": "Utility",
|
||||
"url": "https://civitai.com/models/faceid",
|
||||
"url": "https://civitai.com/models/301776",
|
||||
"tags": [
|
||||
"face-preservation",
|
||||
"identity-consistency"
|
||||
@@ -1561,7 +1561,7 @@
|
||||
{
|
||||
"name": "OmniGen-v2.safetensors",
|
||||
"category": "Utility",
|
||||
"url": "https://huggingface.co/BectorSpaceLab/OmniGen-v2",
|
||||
"url": "https://huggingface.co/BectorSpaceLab/OmniGen-v2/resolve/main/OmniGen-v2.safetensors",
|
||||
"tags": [
|
||||
"multi-image-composition",
|
||||
"instruct-editing"
|
||||
@@ -1579,7 +1579,7 @@
|
||||
{
|
||||
"name": "Hyper-SDXL-Lightning.safetensors",
|
||||
"category": "Utility",
|
||||
"url": "https://huggingface.co/ByteDance/Hyper-SD",
|
||||
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SDXL-Lightning.safetensors",
|
||||
"tags": [
|
||||
"1-step-generation",
|
||||
"real-time-previews"
|
||||
@@ -1597,7 +1597,7 @@
|
||||
{
|
||||
"name": "Segment-Anything-2.safetensors",
|
||||
"category": "Utility",
|
||||
"url": "https://huggingface.co/facebook/sam2",
|
||||
"url": "https://huggingface.co/facebook/sam2/resolve/main/Segment-Anything-2.safetensors",
|
||||
"tags": [
|
||||
"object-masking",
|
||||
"surgical-selection",
|
||||
@@ -1616,7 +1616,7 @@
|
||||
{
|
||||
"name": "Food-Photography-XL.safetensors",
|
||||
"category": "Photography",
|
||||
"url": "https://civitai.com/models/food",
|
||||
"url": "https://civitai.com/models/2300106",
|
||||
"tags": [
|
||||
"restaurant-lighting",
|
||||
"macro-food",
|
||||
@@ -1635,7 +1635,7 @@
|
||||
{
|
||||
"name": "Macro-Nature-XL.safetensors",
|
||||
"category": "Photography",
|
||||
"url": "https://civitai.com/models/macro",
|
||||
"url": "https://civitai.com/models/1501570",
|
||||
"tags": [
|
||||
"insect-detail",
|
||||
"shallow-depth",
|
||||
@@ -1654,7 +1654,7 @@
|
||||
{
|
||||
"name": "Product-Commercial-XL.safetensors",
|
||||
"category": "Photography",
|
||||
"url": "https://civitai.com/models/product",
|
||||
"url": "https://civitai.com/models/2366596",
|
||||
"tags": [
|
||||
"clean-background",
|
||||
"product-lighting",
|
||||
@@ -1673,7 +1673,7 @@
|
||||
{
|
||||
"name": "Mature-Faces-XL-v2.safetensors",
|
||||
"category": "Portrait",
|
||||
"url": "https://civitai.com/models/silver",
|
||||
"url": "https://civitai.com/models/2364595",
|
||||
"tags": [
|
||||
"elderly",
|
||||
"silver-hair",
|
||||
@@ -1692,7 +1692,7 @@
|
||||
{
|
||||
"name": "Fashion-Editorial-XL.safetensors",
|
||||
"category": "Portrait",
|
||||
"url": "https://civitai.com/models/fashion",
|
||||
"url": "https://civitai.com/models/2302224",
|
||||
"tags": [
|
||||
"vogue-style",
|
||||
"high-contrast",
|
||||
@@ -1711,7 +1711,7 @@
|
||||
{
|
||||
"name": "Tarot-Card-Check-XL.safetensors",
|
||||
"category": "Niche",
|
||||
"url": "https://civitai.com/models/tarot",
|
||||
"url": "https://civitai.com/models/1743070",
|
||||
"tags": [
|
||||
"mystical-border",
|
||||
"symbolic-art",
|
||||
@@ -1730,7 +1730,7 @@
|
||||
{
|
||||
"name": "Stained-Glass-v4.safetensors",
|
||||
"category": "Niche",
|
||||
"url": "https://civitai.com/models/glass",
|
||||
"url": "https://civitai.com/models/2378571",
|
||||
"tags": [
|
||||
"backlit",
|
||||
"translucent",
|
||||
@@ -1749,7 +1749,7 @@
|
||||
{
|
||||
"name": "Blueprint-Draft-XL.safetensors",
|
||||
"category": "Niche",
|
||||
"url": "https://civitai.com/models/blueprint",
|
||||
"url": "https://civitai.com/models/236991",
|
||||
"tags": [
|
||||
"technical-drawing",
|
||||
"grid-lines",
|
||||
@@ -1768,7 +1768,7 @@
|
||||
{
|
||||
"name": "Pixel-Art-Ultra-XL.safetensors",
|
||||
"category": "Niche",
|
||||
"url": "https://civitai.com/models/pixelart",
|
||||
"url": "https://civitai.com/models/2355675",
|
||||
"tags": [
|
||||
"retro-gaming",
|
||||
"64-bit",
|
||||
@@ -1787,7 +1787,7 @@
|
||||
{
|
||||
"name": "Graffiti-Wall-Pro-XL.safetensors",
|
||||
"category": "Niche",
|
||||
"url": "https://civitai.com/models/graffiti",
|
||||
"url": "https://civitai.com/models/1690260",
|
||||
"tags": [
|
||||
"spray-paint",
|
||||
"street-art",
|
||||
@@ -1806,7 +1806,7 @@
|
||||
{
|
||||
"name": "Chalkboard-Menu-XL.safetensors",
|
||||
"category": "Niche",
|
||||
"url": "https://civitai.com/models/chalk",
|
||||
"url": "https://civitai.com/models/1269967",
|
||||
"tags": [
|
||||
"dusty-chalk",
|
||||
"handwritten",
|
||||
@@ -1825,7 +1825,7 @@
|
||||
{
|
||||
"name": "Embroidery-Craft-XL.safetensors",
|
||||
"category": "Niche",
|
||||
"url": "https://civitai.com/models/embroidery",
|
||||
"url": "https://civitai.com/models/2333323",
|
||||
"tags": [
|
||||
"sewn-texture",
|
||||
"thread-patterns",
|
||||
@@ -1844,7 +1844,7 @@
|
||||
{
|
||||
"name": "Neon-Signs-XL.safetensors",
|
||||
"category": "Niche",
|
||||
"url": "https://civitai.com/models/neonsign",
|
||||
"url": "https://civitai.com/models/190785",
|
||||
"tags": [
|
||||
"glowing-tubes",
|
||||
"dark-city-streets"
|
||||
@@ -1862,7 +1862,7 @@
|
||||
{
|
||||
"name": "Infographic-Clean-XL.safetensors",
|
||||
"category": "Niche",
|
||||
"url": "https://civitai.com/models/info",
|
||||
"url": "https://civitai.com/models/1606567",
|
||||
"tags": [
|
||||
"charts",
|
||||
"clean-typography",
|
||||
@@ -1881,7 +1881,7 @@
|
||||
{
|
||||
"name": "Qwen2.5-VL-7B-Instruct.safetensors",
|
||||
"category": "Logic",
|
||||
"url": "https://huggingface.co/Qwen/Qwen2.5-VL",
|
||||
"url": "https://huggingface.co/Qwen/Qwen2.5-VL/resolve/main/Qwen2.5-VL-7B-Instruct.safetensors",
|
||||
"tags": [
|
||||
"vlm",
|
||||
"image-understanding",
|
||||
@@ -1900,7 +1900,7 @@
|
||||
{
|
||||
"name": "Llama-4-8B-Vision.safetensors",
|
||||
"category": "Logic",
|
||||
"url": "https://huggingface.co/meta-llama/Llama-4",
|
||||
"url": "https://huggingface.co/meta-llama/Llama-4/resolve/main/Llama-4-8B-Vision.safetensors",
|
||||
"tags": [
|
||||
"reasoning",
|
||||
"captioning",
|
||||
@@ -1919,7 +1919,7 @@
|
||||
{
|
||||
"name": "Medical-Anatomy-XL.safetensors",
|
||||
"category": "Niche",
|
||||
"url": "https://civitai.com/models/medical",
|
||||
"url": "https://civitai.com/models/1854950",
|
||||
"tags": [
|
||||
"scientific-illustration",
|
||||
"biological-accuracy"
|
||||
@@ -1956,7 +1956,7 @@
|
||||
{
|
||||
"name": "Comic-Book-Inker-XL.safetensors",
|
||||
"category": "Niche",
|
||||
"url": "https://civitai.com/models/inker",
|
||||
"url": "https://civitai.com/models/326264",
|
||||
"tags": [
|
||||
"thick-black-lines",
|
||||
"cross-hatching"
|
||||
@@ -1974,7 +1974,7 @@
|
||||
{
|
||||
"name": "Retro-VCR-XL.safetensors",
|
||||
"category": "Niche",
|
||||
"url": "https://civitai.com/models/vcr",
|
||||
"url": "https://civitai.com/models/1421406",
|
||||
"tags": [
|
||||
"glitch-art",
|
||||
"tracking-lines",
|
||||
@@ -1993,7 +1993,7 @@
|
||||
{
|
||||
"name": "Miniature-World-XL.safetensors",
|
||||
"category": "Niche",
|
||||
"url": "https://civitai.com/models/miniature",
|
||||
"url": "https://civitai.com/models/2081244",
|
||||
"tags": [
|
||||
"tilt-shift",
|
||||
"toy-figures",
|
||||
@@ -3364,4 +3364,4 @@
|
||||
"enabled": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+486
-131
@@ -1,22 +1,62 @@
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
# Add ComfyUI root to path to import folder_paths
|
||||
# Assumes this script is in custom_nodes/ComfyUI-Cluster/
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
comfy_root = os.path.abspath(os.path.join(current_dir, "..", ".."))
|
||||
sys.path.append(comfy_root)
|
||||
if comfy_root not in sys.path:
|
||||
sys.path.append(comfy_root)
|
||||
|
||||
try:
|
||||
import folder_paths
|
||||
except ImportError:
|
||||
print("Error: Could not import folder_paths. Make sure this script is running inside the ComfyUI environment or folder structure.")
|
||||
print("Error: Could not import folder_paths.")
|
||||
print("Make sure this script runs inside the ComfyUI folder structure.")
|
||||
print(f"Constructed ComfyUI root path: {comfy_root}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
REGISTRY_FILE = "model_registry.json"
|
||||
CONFIG_FILE = "cluster_config.json"
|
||||
SCAN_LOG_FILE = "model_scan.log"
|
||||
|
||||
CATEGORY_SPECS = [
|
||||
("checkpoints", "checkpoints"),
|
||||
("loras", "loras"),
|
||||
("controlnets", "controlnet"),
|
||||
("vae", "vae"),
|
||||
("text_encoders", "clip"),
|
||||
("diffusion_models", "diffusion_models"),
|
||||
("clip", "clip"),
|
||||
("embeddings", "embeddings"),
|
||||
]
|
||||
|
||||
FOLDER_TYPE_ALIAS = {
|
||||
"checkpoint": "checkpoints",
|
||||
"checkpoints": "checkpoints",
|
||||
"lora": "loras",
|
||||
"loras": "loras",
|
||||
"controlnet": "controlnet",
|
||||
"controlnets": "controlnet",
|
||||
"clip": "clip",
|
||||
"clips": "clip",
|
||||
"text_encoder": "clip",
|
||||
"text_encoders": "clip",
|
||||
"vae": "vae",
|
||||
"unet": "diffusion_models",
|
||||
"diffusion_model": "diffusion_models",
|
||||
"diffusion_models": "diffusion_models",
|
||||
"embedding": "embeddings",
|
||||
"embeddings": "embeddings",
|
||||
}
|
||||
|
||||
_FOLDER_ROOTS_CACHE = {}
|
||||
_FILE_INDEX_CACHE = {}
|
||||
|
||||
|
||||
def load_registry(path):
|
||||
if not os.path.exists(path):
|
||||
@@ -24,10 +64,12 @@ def load_registry(path):
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def save_registry(path, data):
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
|
||||
def load_config():
|
||||
config_path = os.path.join(current_dir, CONFIG_FILE)
|
||||
if os.path.exists(config_path):
|
||||
@@ -35,147 +77,428 @@ def load_config():
|
||||
return json.load(f)
|
||||
return {}
|
||||
|
||||
|
||||
def save_config(data):
|
||||
config_path = os.path.join(current_dir, CONFIG_FILE)
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
def get_user_root():
|
||||
|
||||
def normalize_path(path):
|
||||
return os.path.normpath(os.path.abspath(path))
|
||||
|
||||
|
||||
def normalize_for_compare(path_value):
|
||||
if not isinstance(path_value, str):
|
||||
return ""
|
||||
value = path_value.strip().strip('"')
|
||||
if not value:
|
||||
return ""
|
||||
value = os.path.expandvars(os.path.expanduser(value))
|
||||
if not os.path.isabs(value):
|
||||
value = os.path.join(current_dir, value)
|
||||
return normalize_path(value)
|
||||
|
||||
|
||||
def canonical_folder_type(folder_type, default_type):
|
||||
raw = (folder_type or default_type or "").strip().lower()
|
||||
if not raw:
|
||||
return default_type
|
||||
return FOLDER_TYPE_ALIAS.get(raw, raw)
|
||||
|
||||
|
||||
def expand_folder_types(folder_type):
|
||||
canonical = canonical_folder_type(folder_type, "checkpoints")
|
||||
|
||||
if canonical == "checkpoints":
|
||||
expanded = ["checkpoints", "diffusion_models", "unet"]
|
||||
elif canonical == "diffusion_models":
|
||||
expanded = ["diffusion_models", "unet", "checkpoints"]
|
||||
elif canonical == "clip":
|
||||
expanded = ["clip", "text_encoders", "checkpoints"]
|
||||
elif canonical == "vae":
|
||||
expanded = ["vae", "checkpoints"]
|
||||
elif canonical == "controlnet":
|
||||
expanded = ["controlnet"]
|
||||
elif canonical == "loras":
|
||||
expanded = ["loras"]
|
||||
elif canonical == "embeddings":
|
||||
expanded = ["embeddings"]
|
||||
else:
|
||||
expanded = [canonical]
|
||||
|
||||
if canonical not in expanded:
|
||||
expanded.insert(0, canonical)
|
||||
|
||||
deduped = []
|
||||
seen = set()
|
||||
for ft in expanded:
|
||||
if ft in seen:
|
||||
continue
|
||||
seen.add(ft)
|
||||
deduped.append(ft)
|
||||
return deduped
|
||||
|
||||
|
||||
def get_folder_roots(folder_type):
|
||||
if folder_type in _FOLDER_ROOTS_CACHE:
|
||||
return _FOLDER_ROOTS_CACHE[folder_type]
|
||||
|
||||
roots = []
|
||||
try:
|
||||
roots = folder_paths.get_folder_paths(folder_type) or []
|
||||
except Exception:
|
||||
roots = []
|
||||
|
||||
cleaned = []
|
||||
seen = set()
|
||||
for root in roots:
|
||||
if not root:
|
||||
continue
|
||||
norm = normalize_path(root)
|
||||
if norm in seen:
|
||||
continue
|
||||
seen.add(norm)
|
||||
cleaned.append(norm)
|
||||
|
||||
_FOLDER_ROOTS_CACHE[folder_type] = cleaned
|
||||
return cleaned
|
||||
|
||||
|
||||
def build_filename_index(root):
|
||||
norm_root = normalize_path(root)
|
||||
if norm_root in _FILE_INDEX_CACHE:
|
||||
return _FILE_INDEX_CACHE[norm_root]
|
||||
|
||||
index = {}
|
||||
if os.path.isdir(norm_root):
|
||||
for walk_root, _, files in os.walk(norm_root):
|
||||
for file_name in files:
|
||||
key = file_name.lower()
|
||||
index.setdefault(key, []).append(os.path.join(walk_root, file_name))
|
||||
|
||||
_FILE_INDEX_CACHE[norm_root] = index
|
||||
return index
|
||||
|
||||
|
||||
def is_subpath(path, root):
|
||||
try:
|
||||
path_n = normalize_path(path)
|
||||
root_n = normalize_path(root)
|
||||
return os.path.commonpath([path_n, root_n]) == root_n
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def infer_folder_type_from_path(path, candidate_types, user_root=None):
|
||||
for ft in candidate_types:
|
||||
for root in get_folder_roots(ft):
|
||||
if is_subpath(path, root):
|
||||
return ft
|
||||
|
||||
if user_root and is_subpath(path, user_root):
|
||||
rel = os.path.relpath(path, user_root)
|
||||
first_part = rel.split(os.sep)[0].lower() if rel else ""
|
||||
if first_part:
|
||||
first_part = canonical_folder_type(first_part, first_part)
|
||||
for ft in candidate_types:
|
||||
if canonical_folder_type(ft, ft) == first_part:
|
||||
return ft
|
||||
|
||||
return candidate_types[0] if candidate_types else None
|
||||
|
||||
|
||||
def validate_existing_path(existing_path, expected_name):
|
||||
resolved = normalize_for_compare(existing_path)
|
||||
if not resolved:
|
||||
return None
|
||||
if not os.path.isfile(resolved):
|
||||
return None
|
||||
if os.path.basename(resolved).lower() != expected_name.lower():
|
||||
return None
|
||||
return resolved
|
||||
|
||||
|
||||
def resolve_model_path(filename, preferred_folder_type, user_root=None, current_path=None):
|
||||
if not filename:
|
||||
return (None, preferred_folder_type, "missing_name")
|
||||
|
||||
candidate_types = expand_folder_types(preferred_folder_type)
|
||||
target_name = filename.strip()
|
||||
target_name_l = target_name.lower()
|
||||
|
||||
# 1) If registry path already points to the expected file, trust it.
|
||||
existing = validate_existing_path(current_path, target_name)
|
||||
if existing:
|
||||
inferred = infer_folder_type_from_path(existing, candidate_types, user_root)
|
||||
return (existing, inferred, "registry_path")
|
||||
|
||||
# 2) ComfyUI resolver.
|
||||
for ft in candidate_types:
|
||||
try:
|
||||
resolved = folder_paths.get_full_path(ft, target_name)
|
||||
except Exception:
|
||||
resolved = None
|
||||
if resolved and os.path.isfile(resolved):
|
||||
return (normalize_path(resolved), ft, "comfy_get_full_path")
|
||||
|
||||
# 3) Search ComfyUI configured roots.
|
||||
searched_roots = set()
|
||||
for ft in candidate_types:
|
||||
for root in get_folder_roots(ft):
|
||||
if root in searched_roots:
|
||||
continue
|
||||
searched_roots.add(root)
|
||||
|
||||
direct = os.path.join(root, target_name)
|
||||
if os.path.isfile(direct):
|
||||
return (normalize_path(direct), ft, "comfy_root_exact")
|
||||
|
||||
index = build_filename_index(root)
|
||||
matches = index.get(target_name_l, [])
|
||||
if matches:
|
||||
return (normalize_path(matches[0]), ft, "comfy_root_recursive")
|
||||
|
||||
# 4) Search user root (external models root), if configured.
|
||||
if user_root and os.path.isdir(user_root):
|
||||
direct = os.path.join(user_root, target_name)
|
||||
if os.path.isfile(direct):
|
||||
inferred = infer_folder_type_from_path(direct, candidate_types, user_root)
|
||||
return (normalize_path(direct), inferred, "user_root_direct")
|
||||
|
||||
for ft in candidate_types:
|
||||
sub = os.path.join(user_root, ft, target_name)
|
||||
if os.path.isfile(sub):
|
||||
return (normalize_path(sub), ft, "user_root_subfolder")
|
||||
|
||||
user_index = build_filename_index(user_root)
|
||||
matches = user_index.get(target_name_l, [])
|
||||
if matches:
|
||||
best = normalize_path(matches[0])
|
||||
inferred = infer_folder_type_from_path(best, candidate_types, user_root)
|
||||
return (best, inferred, "user_root_recursive")
|
||||
|
||||
return (None, candidate_types[0], "not_found")
|
||||
|
||||
|
||||
def get_user_root(prompt_if_missing=True):
|
||||
config = load_config()
|
||||
root = config.get("models_root")
|
||||
|
||||
# Verify validity of stored root
|
||||
|
||||
if root and os.path.isdir(root):
|
||||
root = normalize_path(root)
|
||||
print(f"Using configured models root: {root}")
|
||||
return root
|
||||
|
||||
print("\n--- Configuration Required ---")
|
||||
print("Could not automatically locate your models using ComfyUI defaults.")
|
||||
print("Please enter the path to your 'models' directory (e.g. D:\\ComfyUI\\models or I:\\models).")
|
||||
|
||||
|
||||
if not prompt_if_missing:
|
||||
return None
|
||||
|
||||
print("\n--- Optional Configuration ---")
|
||||
print("Could not load a saved models root directory.")
|
||||
print("Enter your models root path if you keep models outside ComfyUI defaults.")
|
||||
print("Press Enter to skip.")
|
||||
|
||||
while True:
|
||||
try:
|
||||
root = input("Models Root Path: ").strip().strip('"') # Remove quotes if user copy-pasted
|
||||
entered = input("Models Root Path: ").strip().strip('"')
|
||||
except EOFError:
|
||||
print("\nInput canceled.")
|
||||
sys.exit(1)
|
||||
|
||||
if os.path.isdir(root):
|
||||
print("Input unavailable. Continuing without custom models root.")
|
||||
return None
|
||||
|
||||
if not entered:
|
||||
print("Skipping custom models root.")
|
||||
return None
|
||||
|
||||
if os.path.isdir(entered):
|
||||
root = normalize_path(entered)
|
||||
config["models_root"] = root
|
||||
save_config(config)
|
||||
print("Configuration saved.\n")
|
||||
return root
|
||||
else:
|
||||
print(f"Error: Directory not found: {root}")
|
||||
print("Please try again.")
|
||||
|
||||
def check_exists(folder_type, filename, user_root=None):
|
||||
# 1. Try ComfyUI internal resolution (preferred)
|
||||
# This checks known paths including extra_model_paths.yaml if loaded correctly
|
||||
try:
|
||||
path = folder_paths.get_full_path(folder_type, filename)
|
||||
if path: return True
|
||||
except:
|
||||
pass # folder_paths might error on unknown types
|
||||
|
||||
# 2. Manual check against User Root
|
||||
if user_root:
|
||||
# Logic: user_root + folder_type + filename
|
||||
# Normalize folder_type to directory name if needed (usually they match: loras -> loras)
|
||||
|
||||
candidates = [folder_type]
|
||||
|
||||
# Add symmetric fallbacks for checkpoints
|
||||
if folder_type in ["checkpoints", "diffusion_models", "unet"]:
|
||||
candidates = ["checkpoints", "diffusion_models", "unet"]
|
||||
|
||||
for d in candidates:
|
||||
# Check direct join
|
||||
p = os.path.join(user_root, d, filename)
|
||||
if os.path.exists(p): return True
|
||||
|
||||
# Recursive / Subdirectory check?
|
||||
# Some users put things in models/checkpoints/SDXL/model.safetensors
|
||||
# But the registry filename is usually just "model.safetensors"
|
||||
# We won't do deep scan for performance unless requested, matching GUI manual approach
|
||||
|
||||
return False
|
||||
print(f"Error: Directory not found: {entered}")
|
||||
|
||||
def sync_models():
|
||||
|
||||
def write_scan_log(
|
||||
log_path,
|
||||
scan_time,
|
||||
user_root,
|
||||
stats,
|
||||
missing_models,
|
||||
status_changes,
|
||||
path_changes,
|
||||
folder_type_changes,
|
||||
dry_run,
|
||||
):
|
||||
with open(log_path, "w", encoding="utf-8") as log:
|
||||
log.write(f"Scan Date: {scan_time}\n")
|
||||
log.write(f"Dry Run: {dry_run}\n")
|
||||
log.write(f"User Models Root: {user_root or '(not set)'}\n")
|
||||
log.write(f"Scanned Items: {stats['scanned']}\n")
|
||||
log.write(f"Status Changes: {stats['status_changes']}\n")
|
||||
log.write(f"Path Changes: {stats['path_changes']}\n")
|
||||
log.write(f"Folder Type Changes: {stats['folder_type_changes']}\n")
|
||||
log.write(f"Missing Models: {stats['missing']}\n")
|
||||
|
||||
log.write("\n== Status Changes ==\n")
|
||||
for entry in status_changes:
|
||||
log.write(f"{entry}\n")
|
||||
|
||||
log.write("\n== Path Changes ==\n")
|
||||
for entry in path_changes:
|
||||
log.write(f"{entry}\n")
|
||||
|
||||
log.write("\n== Folder Type Changes ==\n")
|
||||
for entry in folder_type_changes:
|
||||
log.write(f"{entry}\n")
|
||||
|
||||
log.write("\n== Missing Models ==\n")
|
||||
for entry in missing_models:
|
||||
log.write(f"{entry}\n")
|
||||
|
||||
|
||||
def sync_models(dry_run=False, prompt_for_root=True):
|
||||
registry_path = os.path.join(current_dir, REGISTRY_FILE)
|
||||
log_path = os.path.join(current_dir, "model_scan.log")
|
||||
|
||||
log_path = os.path.join(current_dir, SCAN_LOG_FILE)
|
||||
|
||||
registry = load_registry(registry_path)
|
||||
|
||||
if not registry:
|
||||
print(f"Registry not found at {registry_path}")
|
||||
return
|
||||
|
||||
# Ensure we have a root to check against if Comfy fails
|
||||
user_root = get_user_root()
|
||||
user_root = get_user_root(prompt_if_missing=prompt_for_root)
|
||||
|
||||
print(f"Scanning models for registry: {registry_path}")
|
||||
changes = 0
|
||||
print(f"\nScanning models for registry: {registry_path}")
|
||||
scan_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
stats = {
|
||||
"scanned": 0,
|
||||
"status_changes": 0,
|
||||
"path_changes": 0,
|
||||
"folder_type_changes": 0,
|
||||
"missing": 0,
|
||||
}
|
||||
missing_models = []
|
||||
status_change_details = []
|
||||
path_change_details = []
|
||||
folder_type_change_details = []
|
||||
|
||||
# Helper for processing lists
|
||||
def process_list(category_name, default_folder_type):
|
||||
nonlocal changes
|
||||
for category_name, default_folder_type in CATEGORY_SPECS:
|
||||
items = registry.get(category_name, [])
|
||||
if not items: return
|
||||
if not items:
|
||||
continue
|
||||
|
||||
print(f"Scanning {category_name} ({len(items)} items)...")
|
||||
|
||||
print(f"Scanning {category_name}...")
|
||||
for item in items:
|
||||
name = item.get("name")
|
||||
ftype = item.get("folder_type", default_folder_type)
|
||||
|
||||
# Pass user_root to check_exists
|
||||
exists = check_exists(ftype, name, user_root)
|
||||
|
||||
old_status = item.get("enabled", None)
|
||||
item["enabled"] = exists
|
||||
|
||||
if old_status != exists:
|
||||
changes += 1
|
||||
status_str = "ENABLED" if exists else "DISABLED"
|
||||
print(f" [{status_str}] {name}")
|
||||
|
||||
if not exists:
|
||||
name = str(item.get("name", "")).strip()
|
||||
if not name:
|
||||
continue
|
||||
|
||||
stats["scanned"] += 1
|
||||
|
||||
preferred_ft = canonical_folder_type(item.get("folder_type"), default_folder_type)
|
||||
|
||||
if "folder_type" in item and item.get("folder_type") != preferred_ft:
|
||||
old_ft = item.get("folder_type")
|
||||
item["folder_type"] = preferred_ft
|
||||
stats["folder_type_changes"] += 1
|
||||
folder_type_change_details.append(
|
||||
f"[{category_name}] {name}: {old_ft} -> {preferred_ft} (normalized alias)"
|
||||
)
|
||||
|
||||
resolved_path, resolved_ft, source = resolve_model_path(
|
||||
filename=name,
|
||||
preferred_folder_type=preferred_ft,
|
||||
user_root=user_root,
|
||||
current_path=item.get("path"),
|
||||
)
|
||||
|
||||
exists = resolved_path is not None
|
||||
old_enabled = item.get("enabled")
|
||||
if old_enabled != exists:
|
||||
item["enabled"] = exists
|
||||
stats["status_changes"] += 1
|
||||
status_word = "ENABLED" if exists else "DISABLED"
|
||||
print(f" [{status_word}] {name}")
|
||||
status_change_details.append(
|
||||
f"[{category_name}] {name}: {old_enabled} -> {exists}"
|
||||
)
|
||||
else:
|
||||
item["enabled"] = exists
|
||||
|
||||
if exists:
|
||||
old_path_raw = item.get("path")
|
||||
old_path_norm = normalize_for_compare(old_path_raw)
|
||||
if old_path_norm != resolved_path:
|
||||
item["path"] = resolved_path
|
||||
stats["path_changes"] += 1
|
||||
print(f" [PATH FIXED] {name}")
|
||||
path_change_details.append(
|
||||
f"[{category_name}] {name}: {old_path_raw} -> {resolved_path} ({source})"
|
||||
)
|
||||
|
||||
should_store_folder_type = ("folder_type" in item) or (resolved_ft != default_folder_type)
|
||||
if should_store_folder_type and item.get("folder_type") != resolved_ft:
|
||||
old_ft = item.get("folder_type")
|
||||
item["folder_type"] = resolved_ft
|
||||
stats["folder_type_changes"] += 1
|
||||
print(f" [TYPE FIXED] {name}: {old_ft} -> {resolved_ft}")
|
||||
folder_type_change_details.append(
|
||||
f"[{category_name}] {name}: {old_ft} -> {resolved_ft}"
|
||||
)
|
||||
else:
|
||||
stats["missing"] += 1
|
||||
missing_models.append(f"[{category_name}] {name}")
|
||||
if "path" in item:
|
||||
old_path_raw = item.get("path")
|
||||
if old_path_raw:
|
||||
stats["path_changes"] += 1
|
||||
print(f" [PATH CLEARED] {name}")
|
||||
path_change_details.append(
|
||||
f"[{category_name}] {name}: {old_path_raw} -> (removed, not found)"
|
||||
)
|
||||
item.pop("path", None)
|
||||
|
||||
# Process all categories
|
||||
process_list("checkpoints", "checkpoints")
|
||||
process_list("loras", "loras")
|
||||
process_list("controlnets", "controlnet")
|
||||
process_list("vae", "vae")
|
||||
process_list("text_encoders", "clip")
|
||||
process_list("diffusion_models", "diffusion_models")
|
||||
process_list("clip", "clip")
|
||||
process_list("embeddings", "embeddings")
|
||||
total_changes = stats["status_changes"] + stats["path_changes"] + stats["folder_type_changes"]
|
||||
|
||||
if changes > 0:
|
||||
print(f"\nUpdating registry with {changes} status changes...")
|
||||
if dry_run:
|
||||
print("\nDry run complete. No changes were written.")
|
||||
elif total_changes > 0:
|
||||
print(f"\nUpdating registry with {total_changes} changes...")
|
||||
save_registry(registry_path, registry)
|
||||
print("Done.")
|
||||
else:
|
||||
print("\nAll model statuses are up to date.")
|
||||
|
||||
# Write Log
|
||||
with open(log_path, "w", encoding="utf-8") as log:
|
||||
log.write(f"Scan Date: {os.path.basename(current_dir)}\n")
|
||||
log.write(f"User Models Root: {user_root}\n")
|
||||
log.write(f"Missing Models ({len(missing_models)}):\n")
|
||||
for m in missing_models:
|
||||
log.write(f"{m}\n")
|
||||
|
||||
print("\nAll model statuses and paths are up to date.")
|
||||
|
||||
write_scan_log(
|
||||
log_path=log_path,
|
||||
scan_time=scan_time,
|
||||
user_root=user_root,
|
||||
stats=stats,
|
||||
missing_models=missing_models,
|
||||
status_changes=status_change_details,
|
||||
path_changes=path_change_details,
|
||||
folder_type_changes=folder_type_change_details,
|
||||
dry_run=dry_run,
|
||||
)
|
||||
print(f"\nScan log written to: {log_path}")
|
||||
if missing_models:
|
||||
print(f"WARNING: {len(missing_models)} models were not found. Check log for details.")
|
||||
print(
|
||||
f"Summary: scanned={stats['scanned']}, "
|
||||
f"status_changes={stats['status_changes']}, "
|
||||
f"path_changes={stats['path_changes']}, "
|
||||
f"folder_type_changes={stats['folder_type_changes']}, "
|
||||
f"missing={stats['missing']}"
|
||||
)
|
||||
|
||||
if stats["missing"] > 0:
|
||||
print(f"WARNING: {stats['missing']} models were not found. Check log for details.")
|
||||
|
||||
|
||||
def reset_registry():
|
||||
registry_path = os.path.join(current_dir, REGISTRY_FILE)
|
||||
registry = load_registry(registry_path)
|
||||
|
||||
|
||||
if not registry:
|
||||
print(f"Registry not found at {registry_path}")
|
||||
return
|
||||
@@ -183,43 +506,75 @@ def reset_registry():
|
||||
print("Resetting all models to ENABLED...")
|
||||
count = 0
|
||||
|
||||
for category in ["checkpoints", "loras", "controlnets", "vae", "text_encoders", "diffusion_models", "clip", "embeddings"]:
|
||||
for category, _default_folder_type in CATEGORY_SPECS:
|
||||
if category in registry:
|
||||
for item in registry[category]:
|
||||
item["enabled"] = True
|
||||
count += 1
|
||||
|
||||
|
||||
save_registry(registry_path, registry)
|
||||
print(f"Done. {count} items enabled.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
if sys.argv[1] == "--sync":
|
||||
sync_models()
|
||||
elif sys.argv[1] == "--reset":
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Sync model_registry.json with actual disk paths and availability."
|
||||
)
|
||||
mode = parser.add_mutually_exclusive_group()
|
||||
mode.add_argument(
|
||||
"--sync",
|
||||
action="store_true",
|
||||
help="Scan models, fix path/folder_type, and update enabled status.",
|
||||
)
|
||||
mode.add_argument(
|
||||
"--reset",
|
||||
action="store_true",
|
||||
help="Set enabled=true for all known registry entries.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Run sync logic without writing model_registry.json.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-prompt",
|
||||
action="store_true",
|
||||
help="Do not prompt for models_root when no saved root exists.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def run_interactive_menu():
|
||||
print("\n--- Model Registry Manager ---")
|
||||
print("1. Sync + Repair Paths (write changes)")
|
||||
print("2. Dry Run (preview changes only)")
|
||||
print("3. Reset Registry (enable all)")
|
||||
print("4. Exit")
|
||||
|
||||
while True:
|
||||
try:
|
||||
choice = input("\nSelect option (1-4): ").strip()
|
||||
except EOFError:
|
||||
break
|
||||
|
||||
if choice == "1":
|
||||
sync_models(dry_run=False, prompt_for_root=True)
|
||||
elif choice == "2":
|
||||
sync_models(dry_run=True, prompt_for_root=True)
|
||||
elif choice == "3":
|
||||
reset_registry()
|
||||
elif choice == "4":
|
||||
break
|
||||
else:
|
||||
print("Usage: sync_registry.py [--sync | --reset]")
|
||||
print("Invalid option.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parse_args()
|
||||
|
||||
if args.reset:
|
||||
reset_registry()
|
||||
elif args.sync or args.dry_run:
|
||||
sync_models(dry_run=args.dry_run, prompt_for_root=not args.no_prompt)
|
||||
else:
|
||||
print("\n--- Model Registry Manager ---")
|
||||
print("1. Sync with Disk (Enable installed, Disable missing)")
|
||||
print("2. Reset Registry (Enable ALL models)")
|
||||
print("3. Exit")
|
||||
|
||||
# Interactive mode: Loop until exit
|
||||
while True:
|
||||
try:
|
||||
choice = input("\nSelect option (1-3): ").strip()
|
||||
except EOFError:
|
||||
break
|
||||
|
||||
if choice == "1":
|
||||
sync_models()
|
||||
elif choice == "2":
|
||||
reset_registry()
|
||||
elif choice == "3":
|
||||
break
|
||||
else:
|
||||
print("Invalid option.")
|
||||
|
||||
|
||||
run_interactive_menu()
|
||||
|
||||
Reference in New Issue
Block a user