John6666 commited on
Commit
65be8f7
·
verified ·
1 Parent(s): 9806b9a

Upload 7 files

Browse files
Files changed (3) hide show
  1. README.md +1 -1
  2. app.py +2 -6
  3. civitai_to_hf.py +441 -104
README.md CHANGED
@@ -4,7 +4,7 @@ emoji: 🤗
4
  colorFrom: yellow
5
  colorTo: blue
6
  sdk: gradio
7
- sdk_version: 6.11.0
8
  python_version: "3.10.13"
9
  app_file: app.py
10
  pinned: false
 
4
  colorFrom: yellow
5
  colorTo: blue
6
  sdk: gradio
7
+ sdk_version: 6.13.0
8
  python_version: "3.10.13"
9
  app_file: app.py
10
  pinned: false
app.py CHANGED
@@ -35,10 +35,8 @@ from civitai_to_hf import (
35
  load_all_civitai,
36
  refresh_civitai_key_status,
37
  probe_civitai_api,
38
- probe_civitai_green_api,
39
  update_civitai_gallery_mode,
40
  begin_probe_civitai,
41
- begin_probe_civitai_green,
42
  clear_retry_state,
43
  CIVITAI_TYPE,
44
  CIVITAI_BASEMODEL,
@@ -156,13 +154,12 @@ with gr.Blocks(fill_width=True, delete_cache=(60, 3600)) as demo:
156
  with gr.Row():
157
  use_remaining_button = gr.Button(value="Use Remaining URLs", visible=True, variant="secondary")
158
  use_failed_button = gr.Button(value="Retry Failed Only", visible=True, variant="secondary")
159
- clear_remaining_button = gr.Button(value="Clear Retry State", visible=True, variant="secondary")
160
  with gr.Accordion("Probe / Experimental", open=False, visible=CIVITAI_HF_DEBUG):
161
  probe_md = gr.Markdown(value="", visible=False)
162
  with gr.Row():
163
  smoke_test_button = gr.Button(value="Smoke Test", variant="secondary")
164
  probe_button = gr.Button("Probe Civitai", variant="secondary")
165
- probe_green_button = gr.Button("Probe civitai.green", variant="secondary")
166
  gr.DuplicateButton(value="Duplicate Space")
167
 
168
  heavy_outputs = [uploaded_urls, urls_md, urls_remain, urls_failed, civitai_key_status, session_state]
@@ -193,7 +190,7 @@ with gr.Blocks(fill_width=True, delete_cache=(60, 3600)) as demo:
193
  stop_button.click(fn=None, cancels=[download_evt, smoke_evt], queue=False, api_visibility="undocumented")
194
  use_remaining_button.click(use_remaining_urls, [urls_remain, dl_url], [dl_url], queue=False, api_visibility="undocumented")
195
  use_failed_button.click(use_failed_urls, [urls_failed, dl_url], [dl_url], queue=False, api_visibility="undocumented")
196
- clear_remaining_button.click(clear_retry_state, [session_state], [urls_md, urls_remain, urls_failed, session_state], queue=False, api_visibility="undocumented")
197
 
198
  gr.on(
199
  triggers=[search_civitai_submit.click, search_civitai_query.submit],
@@ -218,7 +215,6 @@ with gr.Blocks(fill_width=True, delete_cache=(60, 3600)) as demo:
218
  search_civitai_user.change(refresh_civitai_creators, [search_civitai_user, civitai_key], [search_civitai_user], queue=False, api_visibility="undocumented")
219
  civitai_key.change(refresh_civitai_key_status, [civitai_key], [civitai_key_status], queue=False, api_visibility="undocumented")
220
  probe_button.click(begin_probe_civitai, [], [probe_md], queue=False, api_visibility="undocumented").then(probe_civitai_api, [search_civitai_query, civitai_key], [probe_md, civitai_key_status], queue=False, api_visibility="undocumented")
221
- probe_green_button.click(begin_probe_civitai_green, [], [probe_md], queue=False, api_visibility="undocumented").then(probe_civitai_green_api, [search_civitai_query, civitai_key], [probe_md, civitai_key_status], queue=False, api_visibility="undocumented")
222
 
223
 
224
  demo.queue()
 
35
  load_all_civitai,
36
  refresh_civitai_key_status,
37
  probe_civitai_api,
 
38
  update_civitai_gallery_mode,
39
  begin_probe_civitai,
 
40
  clear_retry_state,
41
  CIVITAI_TYPE,
42
  CIVITAI_BASEMODEL,
 
154
  with gr.Row():
155
  use_remaining_button = gr.Button(value="Use Remaining URLs", visible=True, variant="secondary")
156
  use_failed_button = gr.Button(value="Retry Failed Only", visible=True, variant="secondary")
157
+ clear_remaining_button = gr.Button(value="Clear Remaining", visible=True, variant="secondary")
158
  with gr.Accordion("Probe / Experimental", open=False, visible=CIVITAI_HF_DEBUG):
159
  probe_md = gr.Markdown(value="", visible=False)
160
  with gr.Row():
161
  smoke_test_button = gr.Button(value="Smoke Test", variant="secondary")
162
  probe_button = gr.Button("Probe Civitai", variant="secondary")
 
163
  gr.DuplicateButton(value="Duplicate Space")
164
 
165
  heavy_outputs = [uploaded_urls, urls_md, urls_remain, urls_failed, civitai_key_status, session_state]
 
190
  stop_button.click(fn=None, cancels=[download_evt, smoke_evt], queue=False, api_visibility="undocumented")
191
  use_remaining_button.click(use_remaining_urls, [urls_remain, dl_url], [dl_url], queue=False, api_visibility="undocumented")
192
  use_failed_button.click(use_failed_urls, [urls_failed, dl_url], [dl_url], queue=False, api_visibility="undocumented")
193
+ clear_remaining_button.click(clear_retry_state, [session_state], [urls_remain, urls_failed, session_state], queue=False, api_visibility="undocumented")
194
 
195
  gr.on(
196
  triggers=[search_civitai_submit.click, search_civitai_query.submit],
 
215
  search_civitai_user.change(refresh_civitai_creators, [search_civitai_user, civitai_key], [search_civitai_user], queue=False, api_visibility="undocumented")
216
  civitai_key.change(refresh_civitai_key_status, [civitai_key], [civitai_key_status], queue=False, api_visibility="undocumented")
217
  probe_button.click(begin_probe_civitai, [], [probe_md], queue=False, api_visibility="undocumented").then(probe_civitai_api, [search_civitai_query, civitai_key], [probe_md, civitai_key_status], queue=False, api_visibility="undocumented")
 
218
 
219
 
220
  demo.queue()
civitai_to_hf.py CHANGED
@@ -45,8 +45,13 @@ SEARCH_DETAIL_SIZE = (960, 1280)
45
  LOAD_ALL_BATCH_SIZE = 8
46
  NULL_IMAGE_PATH = str(Path(__file__).with_name("null.png"))
47
  CIVITAI_DEFAULT_ORIGIN = "https://civitai.com"
 
 
48
  CIVITAI_GREEN_ORIGIN = "https://civitai.green"
49
- CIVITAI_HOST_ALIASES = frozenset({"civitai.com", "www.civitai.com", "civitai.green", "www.civitai.green"})
 
 
 
50
  PREVIEW_VIDEO_EXTS = {".mp4", ".webm", ".mov", ".m4v", ".avi", ".mkv"}
51
  _FFMPEG_PATH = None
52
  _FFMPEG_MISSING_LOGGED = False
@@ -59,6 +64,255 @@ RUN_CANCEL_REGISTRY = {}
59
  RUN_CANCEL_LOCK = threading.Lock()
60
  CREATOR_SUGGEST_CACHE = {}
61
  CREATOR_SUGGEST_LOCK = threading.Lock()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
 
64
  CIVITAI_TYPE = ["Checkpoint", "TextualInversion", "Hypernetwork", "AestheticGradient", "LORA", "LoCon", "DoRA",
@@ -395,7 +649,7 @@ def build_run_outputs(urls, md: str, remain_urls, failed_urls, civitai_key, sess
395
  final_md = md.strip()
396
  if final_md:
397
  final_md += "\n"
398
- return gr.update(value=urls, choices=urls), build_run_status_update(session_state), gr.update(value=final_md), remain_update, failed_update, key_status_update, session_state_output(session_state)
399
 
400
 
401
  def set_stage_progress(progress, current: int, total: int, desc: str):
@@ -546,7 +800,7 @@ def get_civitai_url_parts(url: str):
546
  return urllib.parse.urlsplit("")
547
 
548
  def is_civitai_host(netloc: str):
549
- return str(netloc or "").strip().lower() in CIVITAI_HOST_ALIASES
550
 
551
  def is_civitai_download_api_path(path: str):
552
  return re.match(r'^/api/download/models/\d+$', str(path or "").strip()) is not None
@@ -569,7 +823,7 @@ def extract_civitai_model_version_id(url: str):
569
  def to_civitai_default_download_url(version_id: str, query: str = ""):
570
  if not str(version_id or "").isdigit():
571
  return ""
572
- base = f"{CIVITAI_DEFAULT_ORIGIN}/api/download/models/{version_id}"
573
  return f"{base}?{query}" if query else base
574
 
575
  def normalize_civitai_download_api_url(url: str):
@@ -583,7 +837,7 @@ def extract_first_civitai_download_url_from_html(html: str):
583
  return ""
584
  page = html_lib.unescape(str(html))
585
  patterns = [
586
- r'https?://(?:www\.)?(?:civitai\.com|civitai\.green)/api/download/models/\d+[^\s\'"<>)\]]*',
587
  r"[\"'](/api/download/models/\d+[^\"']*)[\"']",
588
  ]
589
  for pattern in patterns:
@@ -612,7 +866,7 @@ def resolve_civitai_model_page_to_download_url(url: str, api_key: str = ""):
612
  version_id = extract_civitai_model_version_id(raw)
613
  if version_id:
614
  return to_civitai_default_download_url(version_id)
615
- headers = get_civitai_headers(api_key if parts.netloc.lower().endswith("civitai.com") else "")
616
  headers["Referer"] = f"{parts.scheme or 'https'}://{parts.netloc}/"
617
  session = create_retry_session(total=4, backoff_factor=0.8)
618
  try:
@@ -643,18 +897,18 @@ def get_civitai_sha256(dl_url: str, api_key=""):
643
  return k in qs.keys() and qs[k][0] != json.get(k, None) and json.get(k, None) is not None
644
 
645
  if "https://civitai.com/api/download/models/" not in dl_url: return None
646
- base_url = 'https://civitai.com/api/v1/model-versions/'
647
  params = {}
648
  session = create_retry_session(total=6, backoff_factor=1.0)
649
  m = re.match(r'https://civitai.com/api/download/models/(\d+)\??(.+)?', dl_url)
650
  if m is None: return None
651
- url = base_url + m.group(1)
652
  qs = urllib.parse.parse_qs(m.group(2))
653
  if "type" not in qs.keys(): qs["type"] = ["Model"]
654
  try:
655
- r = civitai_get(session, url, api_key=api_key, params=params, timeout=(5.0, 15), label='Civitai sha256')
656
  if not r.ok: return None
657
- json = dict(r.json())
658
  if "files" not in json.keys() or not isinstance(json["files"], list): return None
659
  hash = None
660
  for d in json["files"]:
@@ -1081,7 +1335,7 @@ def sort_civitai_basemodels(items: list[str]):
1081
 
1082
 
1083
  def fetch_civitai_basemodels(api_key: str = "", pages_per_sort: int = CIVITAI_BASEMODEL_REFRESH_PAGES_PER_SORT):
1084
- base_url = 'https://civitai.com/api/v1/models'
1085
  observed = set()
1086
  session = create_retry_session(total=6, backoff_factor=1.0)
1087
  seeds = [("Newest", "AllTime"), ("Most Downloaded", "AllTime")]
@@ -1091,13 +1345,13 @@ def fetch_civitai_basemodels(api_key: str = "", pages_per_sort: int = CIVITAI_BA
1091
  params = {'sort': sort, 'period': period, 'limit': 100, 'page': page_index, 'nsfw': 'true'}
1092
  try:
1093
  if next_url is None:
1094
- r = civitai_get(session, base_url, api_key=api_key, params=params, timeout=(7.0, 30), label=f'Civitai base model refresh sort={sort} page={page_index}')
1095
  else:
1096
- r = civitai_get(session, next_url, api_key=api_key, timeout=(7.0, 30), label=f'Civitai base model refresh sort={sort} page={page_index}')
1097
  if not r.ok:
1098
  print(f"Failed to refresh Civitai base models. sort={sort} page={page_index} status={r.status_code}")
1099
  break
1100
- data = r.json()
1101
  items = data.get('items', [])
1102
  if not isinstance(items, list): break
1103
  for item in items:
@@ -1380,8 +1634,6 @@ def begin_probe_feedback(title: str):
1380
  return gr.update(value=f"### {title}\n- status: probing...", visible=True)
1381
  def begin_probe_civitai():
1382
  return begin_probe_feedback("Civitai Probe")
1383
- def begin_probe_civitai_green():
1384
- return begin_probe_feedback("civitai.green Probe")
1385
  def begin_probe_civitai_keys():
1386
  return begin_probe_feedback("Civitai Key Probe")
1387
  def begin_probe_civitai_url():
@@ -1509,7 +1761,7 @@ def load_all_civitai(state: dict, api_key: str = ""):
1509
  def search_on_civitai(query: str, types: list[str], allow_model: list[str] = [], limit: int = 100,
1510
  sort: str = "Highest Rated", period: str = "AllTime", tag: str = "", user: str = "", page: int = 1,
1511
  filetype: list[str] = [], api_key: str = "", base_origin: str = CIVITAI_DEFAULT_ORIGIN, progress=gr.Progress(track_tqdm=False)):
1512
- base_url = f'{base_origin}/api/v1/models'
1513
  api_sort = sort if sort in {"Highest Rated", "Most Downloaded", "Newest"} else CIVITAI_SORT[0]
1514
  params = {'sort': api_sort, 'period': period, 'limit': int(limit), 'nsfw': 'true'}
1515
  clean_types = [str(t).strip() for t in (types or []) if str(t).strip()]
@@ -1530,19 +1782,19 @@ def search_on_civitai(query: str, types: list[str], allow_model: list[str] = [],
1530
  if page == 0:
1531
  progress(0, desc="Searching page 1...")
1532
  print("Searching page 1...")
1533
- r = civitai_get(session, base_url, api_key=api_key, params=params | {'page': 1}, timeout=(7.0, 30), label='Civitai search page=1', source='search')
1534
  rs.append(r)
1535
  if r.ok:
1536
- json = r.json()
1537
  next_url = json['metadata']['nextPage'] if 'metadata' in json and 'nextPage' in json['metadata'] else None
1538
  i = 2
1539
  while next_url is not None:
1540
  progress(0, desc=f"Searching page {i}...")
1541
  print(f"Searching page {i}...")
1542
- r = civitai_get(session, next_url, api_key=api_key, timeout=(7.0, 30), label=f'Civitai search page={i}', source='search')
1543
  rs.append(r)
1544
  if r.ok:
1545
- json = r.json()
1546
  next_url = json['metadata']['nextPage'] if 'metadata' in json and 'nextPage' in json['metadata'] else None
1547
  else:
1548
  next_url = None
@@ -1552,18 +1804,18 @@ def search_on_civitai(query: str, types: list[str], allow_model: list[str] = [],
1552
  else:
1553
  progress(0, desc="Searching page 1...")
1554
  print("Searching page 1...")
1555
- r = civitai_get(session, base_url, api_key=api_key, params=params, timeout=(7.0, 30), label='Civitai search page=1', source='search')
1556
  rs.append(r)
1557
  except requests.exceptions.ConnectTimeout:
1558
  print("Request timed out.")
1559
  except Exception as e:
1560
  print(e)
1561
  items = []
1562
- origin = base_origin.rstrip('/')
1563
  for r in rs:
1564
  if not r.ok:
1565
  continue
1566
- json = r.json()
1567
  if 'items' not in json:
1568
  continue
1569
  for j in json['items']:
@@ -1586,9 +1838,7 @@ def search_on_civitai(query: str, types: list[str], allow_model: list[str] = [],
1586
  base_item['img_url'] = images[0].get('url', '') or NULL_IMAGE_PATH
1587
  else:
1588
  base_item['img_url'] = NULL_IMAGE_PATH
1589
- model_url = f"{origin}/models/{j.get('id', '')}"
1590
- if model.get('id') is not None:
1591
- model_url += f"?modelVersionId={model.get('id')}"
1592
  desc = str(base_item.get('description', '') or '')
1593
  base_item['md'] = (
1594
  f"Model URL: [{model_url}]({model_url})<br>"
@@ -1603,7 +1853,7 @@ def search_on_civitai(query: str, types: list[str], allow_model: list[str] = [],
1603
  if files:
1604
  for f in files:
1605
  item = base_item.copy()
1606
- item['dl_url'] = f.get('downloadUrl', '')
1607
  item['size_kb'] = f.get('sizeKB', 0.0)
1608
  item['file_type'] = f.get('type', '')
1609
  if len(filetype) != 0 and f.get('type', '') not in set(filetype):
@@ -1611,7 +1861,7 @@ def search_on_civitai(query: str, types: list[str], allow_model: list[str] = [],
1611
  items.append(item)
1612
  else:
1613
  item = base_item.copy()
1614
- item['dl_url'] = model.get('downloadUrl', '')
1615
  items.append(item)
1616
  if sort in CIVITAI_SORT_EXT:
1617
  if sort == "Size":
@@ -1686,17 +1936,17 @@ def get_civitai_json(dl_url: str, is_html: bool=False, image_baseurl: str="", ap
1686
  default = ("", "", "") if is_html else ""
1687
  if "https://civitai.com/api/download/models/" not in dl_url:
1688
  return default
1689
- base_url = 'https://civitai.com/api/v1/model-versions/'
1690
  params = {}
1691
  session = create_retry_session(total=6, backoff_factor=1.0)
1692
  model_id = re.sub(r'https://civitai.com/api/download/models/(\d+)(?:.+)?', r'\1', dl_url)
1693
- url = base_url + model_id
1694
  try:
1695
  reset_civitai_key_status(api_key, source='info')
1696
- r = civitai_get(session, url, api_key=api_key, params=params, timeout=(5.0, 60), label='Civitai model-version', source='info')
1697
  if not r.ok:
1698
  return default
1699
- json = dict(r.json()).copy()
1700
  html = ""
1701
  image = ""
1702
  if "modelId" in json.keys():
@@ -1704,7 +1954,7 @@ def get_civitai_json(dl_url: str, is_html: bool=False, image_baseurl: str="", ap
1704
  if is_civitai_host(original_parts.netloc) and re.match(r'^/models/\d+(?:/[^/?#]+)?/?$', original_parts.path or ""):
1705
  url = original_dl_url
1706
  else:
1707
- url = f"https://civitai.com/models/{json['modelId']}"
1708
  r = civitai_get(session, url, api_key=api_key, params=params, timeout=(5.0, 60), label='Civitai model page', source='info')
1709
  if not r.ok:
1710
  return json, html, image
@@ -1742,12 +1992,13 @@ def get_civitai_json(dl_url: str, is_html: bool=False, image_baseurl: str="", ap
1742
  return default
1743
 
1744
 
1745
- def _load_civitai_choice_list(base_url: str, label: str, source: str, value_key: str, count_key: str,
1746
  query: str="", api_key: str="", limit: int=200,
1747
  timeout: tuple[float, float]=(7.0, 15.0), retry_total: int=6,
1748
  backoff_factor: float=1.0, default: list[str] | None=None,
1749
  cache: dict | None=None, cache_lock: object | None=None,
1750
- cache_ttl_sec: int=0, startup_name: str=""):
 
1751
  clean_query = str(query or "").strip()
1752
  cache_key = clean_query.casefold()
1753
  now = time.time()
@@ -1762,12 +2013,22 @@ def _load_civitai_choice_list(base_url: str, label: str, source: str, value_key:
1762
  params['query'] = clean_query
1763
  session = create_retry_session(total=retry_total, backoff_factor=backoff_factor)
1764
  try:
1765
- r = civitai_get(session, base_url, api_key=api_key, params=params, timeout=timeout, label=label, source=source)
 
 
 
 
 
 
 
 
 
 
1766
  if not r.ok:
1767
  if not clean_query and startup_name:
1768
  print(f"Failed to load {startup_name} from API at startup. HTTP {r.status_code}.")
1769
  return default
1770
- j = dict(r.json()).copy()
1771
  items = j.get('items', []) if isinstance(j.get('items', []), list) else []
1772
  observed = []
1773
  for item in items:
@@ -1790,7 +2051,7 @@ def _load_civitai_choice_list(base_url: str, label: str, source: str, value_key:
1790
 
1791
  def get_civitai_creator(query: str="", api_key: str="", limit: int=CREATOR_SUGGEST_LIMIT):
1792
  return _load_civitai_choice_list(
1793
- base_url='https://civitai.com/api/v1/creators',
1794
  label='Civitai creators',
1795
  source='creators',
1796
  value_key='username',
@@ -1806,6 +2067,7 @@ def get_civitai_creator(query: str="", api_key: str="", limit: int=CREATOR_SUGGE
1806
  cache_lock=CREATOR_SUGGEST_LOCK,
1807
  cache_ttl_sec=CREATOR_CACHE_TTL_SEC,
1808
  startup_name='Civitai creators',
 
1809
  )
1810
 
1811
 
@@ -1827,7 +2089,7 @@ def refresh_civitai_creators(user_value: str="", api_key: str=""):
1827
 
1828
  def get_civitai_tag():
1829
  return _load_civitai_choice_list(
1830
- base_url='https://civitai.com/api/v1/tags',
1831
  label='Civitai tags',
1832
  source='tags',
1833
  value_key='name',
@@ -1961,6 +2223,60 @@ def extract_probe_first_download_url(payload: dict):
1961
  return dl_url, model, version, {}
1962
  return "", {}, {}, {}
1963
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1964
 
1965
  def probe_civitai_keys(api_key: str):
1966
  effective_api_key = get_effective_probe_api_key(api_key)
@@ -1975,10 +2291,12 @@ def probe_civitai_keys(api_key: str):
1975
  return gr.update(value=probe_text, visible=True), gr.update(value=format_civitai_key_status_md(effective_api_key))
1976
  session = create_retry_session(total=4, backoff_factor=0.8)
1977
  try:
 
 
1978
  reset_civitai_key_status(effective_api_key, source="probe-keys")
1979
- resp = civitai_get(
1980
  session,
1981
- f"{CIVITAI_DEFAULT_ORIGIN}/api/v1/models",
1982
  api_key=effective_api_key,
1983
  params={"limit": 1, "sort": "Newest", "period": "AllTime", "nsfw": "true"},
1984
  timeout=(7.0, 20.0),
@@ -2137,63 +2455,130 @@ def probe_civitai_sidecar(url_input: str, fallback_query: str, api_key: str):
2137
  return gr.update(value=probe_text, visible=True), gr.update(value=format_civitai_key_status_md(effective_api_key))
2138
 
2139
  def probe_civitai_api(query: str, api_key: str):
2140
- origin = CIVITAI_DEFAULT_ORIGIN
2141
  effective_api_key = get_effective_probe_api_key(api_key)
2142
  key_source = "input" if str(api_key or "").strip() else ("env" if effective_api_key else "none")
2143
  reset_civitai_key_status(effective_api_key, source="probe")
2144
  probe_query = str(query or "lora").strip() or "lora"
2145
  print(f"Civitai Probe: query={probe_query!r} key_source={key_source}")
2146
- lines = ["### Civitai Probe", f"- origin: {origin}", f"- query: {probe_query}", f"- key source: {key_source}"]
2147
  session = create_retry_session(total=4, backoff_factor=0.8)
 
 
2148
  anon_ok = False
2149
  try:
2150
- page_resp = session.get(f"{origin}/models", params={"query": probe_query}, headers=get_civitai_headers(""), timeout=(7.0, 20.0))
2151
  lines.append(f"- anonymous page: {'ok' if page_resp.ok else 'fail'} ({page_resp.status_code})")
2152
  if page_resp.ok:
2153
  first_path = extract_first_model_path(page_resp.text)
2154
  if first_path:
2155
  lines.append(f"- anonymous first model: {first_path}")
2156
- anon_resp = session.get(
2157
- f"{origin}/api/v1/models",
 
2158
  params={"query": probe_query, "limit": 1, "sort": "Newest", "period": "AllTime", "nsfw": "true"},
2159
- headers=get_civitai_headers(""),
2160
  timeout=(7.0, 20.0),
 
 
2161
  )
2162
- lines.append(f"- anonymous api: {'ok' if anon_resp.ok else 'fail'} ({anon_resp.status_code})")
2163
- anon_ok = bool(anon_resp.ok)
 
2164
  except Exception as e:
2165
  lines.append(f"- anonymous probe: fail ({type(e).__name__}: {e})")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2166
  parsed_keys = parse_civitai_api_keys(effective_api_key)
2167
  if not parsed_keys:
2168
  lines.append("- auth resolve: skipped (no Civitai key)")
 
2169
  probe_text = "\n".join(lines)
2170
  print(probe_text)
2171
  return gr.update(value=probe_text, visible=True), gr.update(value=format_civitai_key_status_md(effective_api_key))
 
2172
  try:
2173
  reset_civitai_key_status(effective_api_key, source="probe-auth")
2174
- auth_resp = civitai_get(
2175
  session,
2176
- f"{origin}/api/v1/models",
2177
  api_key=effective_api_key,
2178
  params={"query": probe_query, "limit": 1, "sort": "Newest", "period": "AllTime", "nsfw": "true"},
2179
  timeout=(7.0, 20.0),
2180
  label='Civitai probe models',
2181
  source='probe-auth',
2182
  )
2183
- lines.append(f"- auth api: {'ok' if auth_resp.ok else 'fail'} ({auth_resp.status_code})")
2184
- if auth_resp.ok:
2185
- payload = auth_resp.json() if auth_resp.content else {}
 
2186
  dl_url, model, version, file_info = extract_probe_first_download_url(payload)
2187
  if dl_url:
2188
  lines.append(f"- auth first file: {model.get('name', '')} / {version.get('name', '')} / {round(float(file_info.get('sizeKB', 0.0) or 0.0) / 1000.0, 2)}MB")
 
 
 
 
 
 
 
 
 
 
 
 
 
2189
  try:
2190
- resolved_url = resolve_civitai_download_url(dl_url, effective_api_key, max_tries=1)
2191
  resolved_host = urllib.parse.urlparse(resolved_url).netloc
2192
  lines.append(f"- auth resolve: ok ({resolved_host})")
 
 
 
 
 
 
 
2193
  except Exception as e:
2194
  lines.append(f"- auth resolve: fail ({type(e).__name__}: {e})")
 
2195
  else:
2196
  lines.append("- auth resolve: skipped (no downloadUrl in first result)")
 
2197
  except Exception as e:
2198
  lines.append(f"- auth probe: fail ({type(e).__name__}: {e})")
2199
  if not anon_ok:
@@ -2201,51 +2586,3 @@ def probe_civitai_api(query: str, api_key: str):
2201
  probe_text = "\n".join(lines)
2202
  print(probe_text)
2203
  return gr.update(value=probe_text, visible=True), gr.update(value=format_civitai_key_status_md(effective_api_key))
2204
- def probe_civitai_green_api(query: str, api_key: str):
2205
- effective_api_key = get_effective_probe_api_key(api_key)
2206
- key_source = "input" if str(api_key or "").strip() else ("env" if effective_api_key else "none")
2207
- probe_input = str(query or "").strip() or "lora"
2208
- print(f"civitai.green Probe: input={probe_input!r} key_source={key_source}")
2209
- lines = ["### civitai.green Probe", f"- input: {probe_input}", f"- key source: {key_source}"]
2210
- session = create_retry_session(total=4, backoff_factor=0.8)
2211
- try:
2212
- parts = get_civitai_url_parts(probe_input)
2213
- if is_civitai_host(parts.netloc):
2214
- lines.append(f"- url host: {parts.netloc}")
2215
- normalized = normalize_civitai_input_url(probe_input, api_key=effective_api_key)
2216
- if normalized != probe_input:
2217
- lines.append(f"- normalized download url: {normalize_civitai_download_api_url(normalized)}")
2218
- if "api/download/models/" in normalized:
2219
- lines.append("- mode: direct-download probe")
2220
- try:
2221
- resolved_url = resolve_civitai_download_url(normalized, effective_api_key, max_tries=1)
2222
- resolved_host = urllib.parse.urlparse(resolved_url).netloc
2223
- lines.append(f"- resolve: ok ({resolved_host})")
2224
- except Exception as e:
2225
- lines.append(f"- resolve: fail ({type(e).__name__}: {e})")
2226
- else:
2227
- lines.append("- mode: page-only probe")
2228
- page_resp = session.get(probe_input, headers=get_civitai_headers(effective_api_key), timeout=(7.0, 20.0))
2229
- lines.append(f"- page: {'ok' if page_resp.ok else 'fail'} ({page_resp.status_code})")
2230
- if page_resp.ok:
2231
- first_path = extract_first_model_path(page_resp.text)
2232
- if first_path:
2233
- lines.append(f"- first model: {first_path}")
2234
- first_download = extract_first_civitai_download_url_from_html(page_resp.text)
2235
- lines.append(f"- embedded download url: {'found' if first_download else 'not found'}")
2236
- else:
2237
- origin = CIVITAI_GREEN_ORIGIN
2238
- lines.append(f"- origin: {origin}")
2239
- lines.append("- mode: query probe")
2240
- page_resp = session.get(f"{origin}/models", params={"query": probe_input}, headers=get_civitai_headers(effective_api_key), timeout=(7.0, 20.0))
2241
- lines.append(f"- page: {'ok' if page_resp.ok else 'fail'} ({page_resp.status_code})")
2242
- if page_resp.ok:
2243
- first_path = extract_first_model_path(page_resp.text)
2244
- if first_path:
2245
- lines.append(f"- first model: {first_path}")
2246
- lines.append("- api: skipped (HTML-only probe)")
2247
- except Exception as e:
2248
- lines.append(f"- probe: fail ({type(e).__name__}: {e})")
2249
- probe_text = "\n".join(lines)
2250
- print(probe_text)
2251
- return gr.update(value=probe_text, visible=True), gr.update(value=format_civitai_key_status_md(effective_api_key))
 
45
  LOAD_ALL_BATCH_SIZE = 8
46
  NULL_IMAGE_PATH = str(Path(__file__).with_name("null.png"))
47
  CIVITAI_DEFAULT_ORIGIN = "https://civitai.com"
48
+ CIVITAI_CANONICAL_WEB_ORIGIN = CIVITAI_DEFAULT_ORIGIN
49
+ CIVITAI_RED_ORIGIN = "https://civitai.red"
50
  CIVITAI_GREEN_ORIGIN = "https://civitai.green"
51
+ CIVITAI_GREEN_HOST_ALIASES = frozenset({"civitai.green", "www.civitai.green"})
52
+ CIVITAI_RED_HOST_ALIASES = frozenset({"civitai.red", "www.civitai.red"})
53
+ CIVITAI_HOST_ALIASES = frozenset({"civitai.com", "www.civitai.com", *CIVITAI_GREEN_HOST_ALIASES, *CIVITAI_RED_HOST_ALIASES})
54
+ CIVITAI_API_ORIGIN_CANDIDATES = (CIVITAI_RED_ORIGIN, CIVITAI_DEFAULT_ORIGIN)
55
  PREVIEW_VIDEO_EXTS = {".mp4", ".webm", ".mov", ".m4v", ".avi", ".mkv"}
56
  _FFMPEG_PATH = None
57
  _FFMPEG_MISSING_LOGGED = False
 
64
  RUN_CANCEL_LOCK = threading.Lock()
65
  CREATOR_SUGGEST_CACHE = {}
66
  CREATOR_SUGGEST_LOCK = threading.Lock()
67
+ CIVITAI_ACTIVE_API_ORIGIN = ""
68
+ CIVITAI_ACTIVE_API_BASE = ""
69
+ _CIVITAI_API_LOCK = threading.Lock()
70
+
71
+
72
+ def canonicalize_civitai_netloc(netloc: str):
73
+ host = str(netloc or "").strip().lower()
74
+ if host in CIVITAI_GREEN_HOST_ALIASES or host == "www.civitai.com":
75
+ return "civitai.com"
76
+ if host in CIVITAI_RED_HOST_ALIASES:
77
+ return "civitai.red"
78
+ return host
79
+
80
+
81
+ def canonicalize_civitai_host(netloc: str):
82
+ return canonicalize_civitai_netloc(netloc)
83
+
84
+
85
+ def normalize_civitai_origin(value: str):
86
+ raw = str(value or "").strip()
87
+ if not raw:
88
+ return CIVITAI_DEFAULT_ORIGIN
89
+ parts = urllib.parse.urlsplit(raw if "://" in raw else f"https://{raw}")
90
+ host = canonicalize_civitai_netloc(parts.netloc or parts.path)
91
+ if host in {"civitai.com", "civitai.red"}:
92
+ return f"https://{host}"
93
+ return CIVITAI_DEFAULT_ORIGIN
94
+
95
+
96
+ def get_civitai_canonical_web_origin():
97
+ return CIVITAI_CANONICAL_WEB_ORIGIN
98
+
99
+
100
+ def get_civitai_display_origin():
101
+ return get_civitai_canonical_web_origin()
102
+
103
+
104
+ def build_civitai_api_base(origin: str):
105
+ raw = str(origin or "").strip().rstrip("/")
106
+ return f"{raw}/api/v1" if raw else ""
107
+
108
+
109
+ def set_civitai_active_api_origin(origin: str):
110
+ global CIVITAI_ACTIVE_API_ORIGIN, CIVITAI_ACTIVE_API_BASE
111
+ normalized = normalize_civitai_origin(origin)
112
+ if normalized not in CIVITAI_API_ORIGIN_CANDIDATES:
113
+ normalized = CIVITAI_DEFAULT_ORIGIN
114
+ base = build_civitai_api_base(normalized)
115
+ with _CIVITAI_API_LOCK:
116
+ CIVITAI_ACTIVE_API_ORIGIN = normalized
117
+ CIVITAI_ACTIVE_API_BASE = base
118
+ return base
119
+
120
+
121
+ def set_civitai_api_origin(origin: str):
122
+ set_civitai_active_api_origin(origin)
123
+ return CIVITAI_ACTIVE_API_ORIGIN
124
+
125
+
126
+ def get_civitai_api_origin():
127
+ with _CIVITAI_API_LOCK:
128
+ return CIVITAI_ACTIVE_API_ORIGIN or ""
129
+
130
+
131
+ def probe_civitai_api_origin(session, origin: str, timeout: tuple[float, float] = (3.0, 8.0)):
132
+ response = None
133
+ try:
134
+ base_url = build_civitai_api_base(origin)
135
+ if not base_url:
136
+ return False
137
+ response = session.get(
138
+ f"{base_url}/tags",
139
+ params={"limit": 1},
140
+ headers=get_civitai_headers(""),
141
+ timeout=timeout,
142
+ )
143
+ if not response.ok:
144
+ return False
145
+ content_type = str(response.headers.get("content-type") or "").lower()
146
+ if "json" not in content_type:
147
+ return False
148
+ data = response.json()
149
+ return isinstance(data, dict)
150
+ except Exception:
151
+ return False
152
+ finally:
153
+ try:
154
+ if response is not None:
155
+ response.close()
156
+ except Exception:
157
+ pass
158
+
159
+
160
+ def get_civitai_active_api_origin(force_refresh: bool = False, session=None):
161
+ cached_origin = get_civitai_api_origin()
162
+ if cached_origin and not force_refresh:
163
+ return cached_origin
164
+ if session is None:
165
+ session = create_retry_session(total=4, backoff_factor=0.8)
166
+ for origin in CIVITAI_API_ORIGIN_CANDIDATES:
167
+ if probe_civitai_api_origin(session, origin):
168
+ set_civitai_active_api_origin(origin)
169
+ return get_civitai_api_origin()
170
+ set_civitai_active_api_origin(CIVITAI_DEFAULT_ORIGIN)
171
+ return get_civitai_api_origin()
172
+
173
+
174
+ def resolve_civitai_api_origin(session=None):
175
+ return get_civitai_active_api_origin(session=session)
176
+
177
+
178
+ def get_civitai_active_api_base(force_refresh: bool = False, session=None):
179
+ with _CIVITAI_API_LOCK:
180
+ cached_base = CIVITAI_ACTIVE_API_BASE or ""
181
+ if cached_base and not force_refresh:
182
+ return cached_base
183
+ get_civitai_active_api_origin(force_refresh=force_refresh, session=session)
184
+ with _CIVITAI_API_LOCK:
185
+ return CIVITAI_ACTIVE_API_BASE or build_civitai_api_base(CIVITAI_DEFAULT_ORIGIN)
186
+
187
+
188
+ def get_civitai_api_candidate_origins(preferred: str = ""):
189
+ origins = list(CIVITAI_API_ORIGIN_CANDIDATES)
190
+ preferred_origin = normalize_civitai_origin(preferred) if preferred else ""
191
+ if preferred_origin in origins:
192
+ return [preferred_origin] + [origin for origin in origins if origin != preferred_origin]
193
+ cached_origin = get_civitai_api_origin()
194
+ if cached_origin in origins:
195
+ return [cached_origin] + [origin for origin in origins if origin != cached_origin]
196
+ return origins
197
+
198
+
199
+ def iter_civitai_api_bases(preferred: str = "", session=None):
200
+ preferred_origin = normalize_civitai_origin(preferred) if preferred else get_civitai_active_api_origin(session=session)
201
+ return [build_civitai_api_base(origin) for origin in get_civitai_api_candidate_origins(preferred_origin)]
202
+
203
+
204
+ def build_civitai_api_url(path: str, origin: str = ""):
205
+ clean_path = "/" + str(path or "").strip().lstrip("/")
206
+ if not clean_path.startswith("/api/"):
207
+ clean_path = f"/api/v1{clean_path}"
208
+ base_origin = normalize_civitai_origin(origin) if origin else normalize_civitai_origin(get_civitai_api_origin() or CIVITAI_DEFAULT_ORIGIN)
209
+ return f"{base_origin.rstrip('/')}" + clean_path
210
+
211
+
212
+ def build_civitai_model_url(model_id, model_version_id=None):
213
+ if model_id is None:
214
+ return ""
215
+ url = f"{get_civitai_canonical_web_origin().rstrip('/')}/models/{model_id}"
216
+ if model_version_id is not None:
217
+ url += f"?modelVersionId={model_version_id}"
218
+ return url
219
+
220
+
221
+ def should_fallback_civitai_api_response(response):
222
+ if response is None:
223
+ return True
224
+ try:
225
+ status_code = int(getattr(response, "status_code", 0) or 0)
226
+ except Exception:
227
+ return True
228
+ return status_code in {404, 405, 408, 429} or status_code >= 500
229
+
230
+
231
+ def request_civitai_api(session, path: str, api_key: str = "", params=None, timeout: tuple[float, float] = (7.0, 30.0),
232
+ label: str = "Civitai API", source: str = "civitai-api", preferred_origin: str = "",
233
+ non_json_fallback_origin: str = ""):
234
+ preferred = normalize_civitai_origin(preferred_origin) if preferred_origin else get_civitai_active_api_origin(session=session)
235
+ non_json_fallback = normalize_civitai_origin(non_json_fallback_origin) if non_json_fallback_origin else ""
236
+ last_response = None
237
+ last_exception = None
238
+ for origin in get_civitai_api_candidate_origins(preferred):
239
+ url = build_civitai_api_url(path, origin=origin)
240
+ try:
241
+ response = civitai_get(session, url, api_key=api_key, params=params, timeout=timeout, label=label, source=source)
242
+ last_response = response
243
+ if response is not None and response.ok:
244
+ try:
245
+ get_civitai_response_json(response)
246
+ except Exception as e:
247
+ last_exception = e
248
+ log_line("retry", f"{label}: non-json response from {origin}")
249
+ try:
250
+ response.close()
251
+ except Exception:
252
+ pass
253
+ if non_json_fallback and origin != non_json_fallback:
254
+ fallback_url = build_civitai_api_url(path, origin=non_json_fallback)
255
+ try:
256
+ fallback_response = civitai_get(session, fallback_url, api_key=api_key, params=params, timeout=timeout, label=label, source=source)
257
+ last_response = fallback_response
258
+ if fallback_response is not None and fallback_response.ok:
259
+ get_civitai_response_json(fallback_response)
260
+ set_civitai_active_api_origin(non_json_fallback)
261
+ return fallback_response
262
+ if fallback_response is not None and not should_fallback_civitai_api_response(fallback_response):
263
+ return fallback_response
264
+ if fallback_response is not None:
265
+ fallback_response.close()
266
+ except Exception as fallback_error:
267
+ last_exception = fallback_error
268
+ continue
269
+ set_civitai_active_api_origin(origin)
270
+ return response
271
+ if not should_fallback_civitai_api_response(response):
272
+ return response
273
+ if response is not None:
274
+ response.close()
275
+ except Exception as e:
276
+ last_exception = e
277
+ continue
278
+ if last_exception is not None:
279
+ raise last_exception
280
+ if last_response is not None:
281
+ return last_response
282
+ return None
283
+
284
+
285
+ _CIVITAI_JSON_MISSING = object()
286
+
287
+
288
+ def get_civitai_response_json(response, default=_CIVITAI_JSON_MISSING):
289
+ if response is None:
290
+ if default is not _CIVITAI_JSON_MISSING:
291
+ return default
292
+ raise ValueError("Civitai response is missing")
293
+ cached = getattr(response, "_civitai_json", _CIVITAI_JSON_MISSING)
294
+ if cached is not _CIVITAI_JSON_MISSING:
295
+ return cached
296
+ try:
297
+ payload = response.json()
298
+ except Exception:
299
+ if default is not _CIVITAI_JSON_MISSING:
300
+ return default
301
+ raise
302
+ setattr(response, "_civitai_json", payload)
303
+ return payload
304
+
305
+
306
+ def request_civitai_api_url(session, url: str, api_key: str = "", params=None, timeout: tuple[float, float] = (7.0, 30.0),
307
+ label: str = "Civitai API", source: str = "civitai-api"):
308
+ parts = get_civitai_url_parts(url)
309
+ path = str(parts.path or "")
310
+ if is_civitai_host(parts.netloc) and path.startswith("/api/"):
311
+ api_path = path + (f"?{parts.query}" if parts.query else "")
312
+ return request_civitai_api(session, api_path, api_key=api_key, params=params, timeout=timeout, label=label, source=source)
313
+ response = civitai_get(session, url, api_key=api_key, params=params, timeout=timeout, label=label, source=source)
314
+ get_civitai_response_json(response)
315
+ return response
316
 
317
 
318
  CIVITAI_TYPE = ["Checkpoint", "TextualInversion", "Hypernetwork", "AestheticGradient", "LORA", "LoCon", "DoRA",
 
649
  final_md = md.strip()
650
  if final_md:
651
  final_md += "\n"
652
+ return gr.update(value=urls, choices=urls), gr.update(value=final_md), remain_update, failed_update, key_status_update, session_state_output(session_state)
653
 
654
 
655
  def set_stage_progress(progress, current: int, total: int, desc: str):
 
800
  return urllib.parse.urlsplit("")
801
 
802
  def is_civitai_host(netloc: str):
803
+ return canonicalize_civitai_host(netloc) in {"civitai.com", "civitai.red"}
804
 
805
  def is_civitai_download_api_path(path: str):
806
  return re.match(r'^/api/download/models/\d+$', str(path or "").strip()) is not None
 
823
  def to_civitai_default_download_url(version_id: str, query: str = ""):
824
  if not str(version_id or "").isdigit():
825
  return ""
826
+ base = f"{get_civitai_display_origin()}/api/download/models/{version_id}"
827
  return f"{base}?{query}" if query else base
828
 
829
  def normalize_civitai_download_api_url(url: str):
 
837
  return ""
838
  page = html_lib.unescape(str(html))
839
  patterns = [
840
+ r'https?://(?:www\.)?(?:civitai\.com|civitai\.red|civitai\.green)/api/download/models/\d+[^\s\'"<>)\]]*',
841
  r"[\"'](/api/download/models/\d+[^\"']*)[\"']",
842
  ]
843
  for pattern in patterns:
 
866
  version_id = extract_civitai_model_version_id(raw)
867
  if version_id:
868
  return to_civitai_default_download_url(version_id)
869
+ headers = get_civitai_headers(api_key if canonicalize_civitai_host(parts.netloc) in {"civitai.com", "civitai.red"} else "")
870
  headers["Referer"] = f"{parts.scheme or 'https'}://{parts.netloc}/"
871
  session = create_retry_session(total=4, backoff_factor=0.8)
872
  try:
 
897
  return k in qs.keys() and qs[k][0] != json.get(k, None) and json.get(k, None) is not None
898
 
899
  if "https://civitai.com/api/download/models/" not in dl_url: return None
900
+ base_path = '/model-versions'
901
  params = {}
902
  session = create_retry_session(total=6, backoff_factor=1.0)
903
  m = re.match(r'https://civitai.com/api/download/models/(\d+)\??(.+)?', dl_url)
904
  if m is None: return None
905
+ url = f"{base_path}/{m.group(1)}"
906
  qs = urllib.parse.parse_qs(m.group(2))
907
  if "type" not in qs.keys(): qs["type"] = ["Model"]
908
  try:
909
+ r = request_civitai_api(session, url, api_key=api_key, params=params, timeout=(5.0, 15), label='Civitai sha256')
910
  if not r.ok: return None
911
+ json = dict(get_civitai_response_json(r, default={}) or {})
912
  if "files" not in json.keys() or not isinstance(json["files"], list): return None
913
  hash = None
914
  for d in json["files"]:
 
1335
 
1336
 
1337
  def fetch_civitai_basemodels(api_key: str = "", pages_per_sort: int = CIVITAI_BASEMODEL_REFRESH_PAGES_PER_SORT):
1338
+ base_path = '/models'
1339
  observed = set()
1340
  session = create_retry_session(total=6, backoff_factor=1.0)
1341
  seeds = [("Newest", "AllTime"), ("Most Downloaded", "AllTime")]
 
1345
  params = {'sort': sort, 'period': period, 'limit': 100, 'page': page_index, 'nsfw': 'true'}
1346
  try:
1347
  if next_url is None:
1348
+ r = request_civitai_api(session, base_path, api_key=api_key, params=params, timeout=(7.0, 30), label=f'Civitai base model refresh sort={sort} page={page_index}', source='base-models')
1349
  else:
1350
+ r = request_civitai_api_url(session, next_url, api_key=api_key, timeout=(7.0, 30), label=f'Civitai base model refresh sort={sort} page={page_index}', source='base-models')
1351
  if not r.ok:
1352
  print(f"Failed to refresh Civitai base models. sort={sort} page={page_index} status={r.status_code}")
1353
  break
1354
+ data = get_civitai_response_json(r, default={}) or {}
1355
  items = data.get('items', [])
1356
  if not isinstance(items, list): break
1357
  for item in items:
 
1634
  return gr.update(value=f"### {title}\n- status: probing...", visible=True)
1635
  def begin_probe_civitai():
1636
  return begin_probe_feedback("Civitai Probe")
 
 
1637
  def begin_probe_civitai_keys():
1638
  return begin_probe_feedback("Civitai Key Probe")
1639
  def begin_probe_civitai_url():
 
1761
  def search_on_civitai(query: str, types: list[str], allow_model: list[str] = [], limit: int = 100,
1762
  sort: str = "Highest Rated", period: str = "AllTime", tag: str = "", user: str = "", page: int = 1,
1763
  filetype: list[str] = [], api_key: str = "", base_origin: str = CIVITAI_DEFAULT_ORIGIN, progress=gr.Progress(track_tqdm=False)):
1764
+ base_path = '/models'
1765
  api_sort = sort if sort in {"Highest Rated", "Most Downloaded", "Newest"} else CIVITAI_SORT[0]
1766
  params = {'sort': api_sort, 'period': period, 'limit': int(limit), 'nsfw': 'true'}
1767
  clean_types = [str(t).strip() for t in (types or []) if str(t).strip()]
 
1782
  if page == 0:
1783
  progress(0, desc="Searching page 1...")
1784
  print("Searching page 1...")
1785
+ r = request_civitai_api(session, base_path, api_key=api_key, params=params | {'page': 1}, timeout=(7.0, 30), label='Civitai search page=1', source='search')
1786
  rs.append(r)
1787
  if r.ok:
1788
+ json = get_civitai_response_json(r, default={}) or {}
1789
  next_url = json['metadata']['nextPage'] if 'metadata' in json and 'nextPage' in json['metadata'] else None
1790
  i = 2
1791
  while next_url is not None:
1792
  progress(0, desc=f"Searching page {i}...")
1793
  print(f"Searching page {i}...")
1794
+ r = request_civitai_api_url(session, next_url, api_key=api_key, timeout=(7.0, 30), label=f'Civitai search page={i}', source='search')
1795
  rs.append(r)
1796
  if r.ok:
1797
+ json = get_civitai_response_json(r, default={}) or {}
1798
  next_url = json['metadata']['nextPage'] if 'metadata' in json and 'nextPage' in json['metadata'] else None
1799
  else:
1800
  next_url = None
 
1804
  else:
1805
  progress(0, desc="Searching page 1...")
1806
  print("Searching page 1...")
1807
+ r = request_civitai_api(session, base_path, api_key=api_key, params=params, timeout=(7.0, 30), label='Civitai search page=1', source='search')
1808
  rs.append(r)
1809
  except requests.exceptions.ConnectTimeout:
1810
  print("Request timed out.")
1811
  except Exception as e:
1812
  print(e)
1813
  items = []
1814
+ origin = get_civitai_display_origin().rstrip('/')
1815
  for r in rs:
1816
  if not r.ok:
1817
  continue
1818
+ json = get_civitai_response_json(r, default={}) or {}
1819
  if 'items' not in json:
1820
  continue
1821
  for j in json['items']:
 
1838
  base_item['img_url'] = images[0].get('url', '') or NULL_IMAGE_PATH
1839
  else:
1840
  base_item['img_url'] = NULL_IMAGE_PATH
1841
+ model_url = build_civitai_model_url(j.get('id', ''), model.get('id'))
 
 
1842
  desc = str(base_item.get('description', '') or '')
1843
  base_item['md'] = (
1844
  f"Model URL: [{model_url}]({model_url})<br>"
 
1853
  if files:
1854
  for f in files:
1855
  item = base_item.copy()
1856
+ item['dl_url'] = normalize_civitai_download_api_url(f.get('downloadUrl', ''))
1857
  item['size_kb'] = f.get('sizeKB', 0.0)
1858
  item['file_type'] = f.get('type', '')
1859
  if len(filetype) != 0 and f.get('type', '') not in set(filetype):
 
1861
  items.append(item)
1862
  else:
1863
  item = base_item.copy()
1864
+ item['dl_url'] = normalize_civitai_download_api_url(model.get('downloadUrl', ''))
1865
  items.append(item)
1866
  if sort in CIVITAI_SORT_EXT:
1867
  if sort == "Size":
 
1936
  default = ("", "", "") if is_html else ""
1937
  if "https://civitai.com/api/download/models/" not in dl_url:
1938
  return default
1939
+ base_path = '/model-versions'
1940
  params = {}
1941
  session = create_retry_session(total=6, backoff_factor=1.0)
1942
  model_id = re.sub(r'https://civitai.com/api/download/models/(\d+)(?:.+)?', r'\1', dl_url)
1943
+ url = f"{base_path}/{model_id}"
1944
  try:
1945
  reset_civitai_key_status(api_key, source='info')
1946
+ r = request_civitai_api(session, url, api_key=api_key, params=params, timeout=(5.0, 60), label='Civitai model-version', source='info')
1947
  if not r.ok:
1948
  return default
1949
+ json = dict(get_civitai_response_json(r, default={}) or {}).copy()
1950
  html = ""
1951
  image = ""
1952
  if "modelId" in json.keys():
 
1954
  if is_civitai_host(original_parts.netloc) and re.match(r'^/models/\d+(?:/[^/?#]+)?/?$', original_parts.path or ""):
1955
  url = original_dl_url
1956
  else:
1957
+ url = build_civitai_model_url(json['modelId'])
1958
  r = civitai_get(session, url, api_key=api_key, params=params, timeout=(5.0, 60), label='Civitai model page', source='info')
1959
  if not r.ok:
1960
  return json, html, image
 
1992
  return default
1993
 
1994
 
1995
+ def _load_civitai_choice_list(api_path: str, label: str, source: str, value_key: str, count_key: str,
1996
  query: str="", api_key: str="", limit: int=200,
1997
  timeout: tuple[float, float]=(7.0, 15.0), retry_total: int=6,
1998
  backoff_factor: float=1.0, default: list[str] | None=None,
1999
  cache: dict | None=None, cache_lock: object | None=None,
2000
+ cache_ttl_sec: int=0, startup_name: str="", preferred_origin: str="",
2001
+ non_json_fallback_origin: str=""):
2002
  clean_query = str(query or "").strip()
2003
  cache_key = clean_query.casefold()
2004
  now = time.time()
 
2013
  params['query'] = clean_query
2014
  session = create_retry_session(total=retry_total, backoff_factor=backoff_factor)
2015
  try:
2016
+ r = request_civitai_api(
2017
+ session,
2018
+ api_path,
2019
+ api_key=api_key,
2020
+ params=params,
2021
+ timeout=timeout,
2022
+ label=label,
2023
+ source=source,
2024
+ preferred_origin=preferred_origin,
2025
+ non_json_fallback_origin=non_json_fallback_origin,
2026
+ )
2027
  if not r.ok:
2028
  if not clean_query and startup_name:
2029
  print(f"Failed to load {startup_name} from API at startup. HTTP {r.status_code}.")
2030
  return default
2031
+ j = dict(get_civitai_response_json(r, default={}) or {}).copy()
2032
  items = j.get('items', []) if isinstance(j.get('items', []), list) else []
2033
  observed = []
2034
  for item in items:
 
2051
 
2052
  def get_civitai_creator(query: str="", api_key: str="", limit: int=CREATOR_SUGGEST_LIMIT):
2053
  return _load_civitai_choice_list(
2054
+ api_path='/creators',
2055
  label='Civitai creators',
2056
  source='creators',
2057
  value_key='username',
 
2067
  cache_lock=CREATOR_SUGGEST_LOCK,
2068
  cache_ttl_sec=CREATOR_CACHE_TTL_SEC,
2069
  startup_name='Civitai creators',
2070
+ non_json_fallback_origin=CIVITAI_DEFAULT_ORIGIN,
2071
  )
2072
 
2073
 
 
2089
 
2090
  def get_civitai_tag():
2091
  return _load_civitai_choice_list(
2092
+ api_path='/tags',
2093
  label='Civitai tags',
2094
  source='tags',
2095
  value_key='name',
 
2223
  return dl_url, model, version, {}
2224
  return "", {}, {}, {}
2225
 
2226
+ def probe_json_api_step(session, path: str, *, api_key: str = "", params=None, timeout: tuple[float, float] = (7.0, 20.0),
2227
+ label: str = "Civitai probe", source: str = "probe", preferred_origin: str = "",
2228
+ non_json_fallback_origin: str = ""):
2229
+ response = None
2230
+ try:
2231
+ response = request_civitai_api(
2232
+ session,
2233
+ path,
2234
+ api_key=api_key,
2235
+ params=params,
2236
+ timeout=timeout,
2237
+ label=label,
2238
+ source=source,
2239
+ preferred_origin=preferred_origin,
2240
+ non_json_fallback_origin=non_json_fallback_origin,
2241
+ )
2242
+ ok = bool(response is not None and getattr(response, "ok", False))
2243
+ status = str(getattr(response, "status_code", "-"))
2244
+ response_url = str(getattr(response, "url", "") or "")
2245
+ response_host = urllib.parse.urlparse(response_url).netloc
2246
+ json_ok = False
2247
+ if ok:
2248
+ payload = get_civitai_response_json(response, default=_CIVITAI_JSON_MISSING)
2249
+ json_ok = payload is not _CIVITAI_JSON_MISSING
2250
+ return {"ok": ok, "status": status, "host": response_host, "json_ok": json_ok, "error": ""}
2251
+ except Exception as e:
2252
+ return {"ok": False, "status": "-", "host": "", "json_ok": False, "error": f"{type(e).__name__}: {e}"}
2253
+ finally:
2254
+ try:
2255
+ if response is not None:
2256
+ response.close()
2257
+ except Exception:
2258
+ pass
2259
+
2260
+
2261
+ def probe_download_url_step(session, download_url: str, *, api_key: str = "", timeout: tuple[float, float] = (7.0, 20.0)):
2262
+ response = None
2263
+ try:
2264
+ response = civitai_get(session, download_url, api_key=api_key, timeout=timeout, label='Civitai probe download', source='probe-download')
2265
+ status = str(getattr(response, "status_code", "-"))
2266
+ status_code = int(status) if str(status).isdigit() else 0
2267
+ response_url = str(getattr(response, "url", "") or download_url or "")
2268
+ response_host = urllib.parse.urlparse(response_url).netloc
2269
+ ok = bool(response is not None and (getattr(response, "ok", False) or status_code in {301, 302, 303, 307, 308}))
2270
+ return {"ok": ok, "status": status, "host": response_host, "error": ""}
2271
+ except Exception as e:
2272
+ return {"ok": False, "status": "-", "host": "", "error": f"{type(e).__name__}: {e}"}
2273
+ finally:
2274
+ try:
2275
+ if response is not None:
2276
+ response.close()
2277
+ except Exception:
2278
+ pass
2279
+
2280
 
2281
  def probe_civitai_keys(api_key: str):
2282
  effective_api_key = get_effective_probe_api_key(api_key)
 
2291
  return gr.update(value=probe_text, visible=True), gr.update(value=format_civitai_key_status_md(effective_api_key))
2292
  session = create_retry_session(total=4, backoff_factor=0.8)
2293
  try:
2294
+ api_origin = resolve_civitai_api_origin(session)
2295
+ lines.append(f"- api origin: {api_origin}")
2296
  reset_civitai_key_status(effective_api_key, source="probe-keys")
2297
+ resp = request_civitai_api(
2298
  session,
2299
+ "/models",
2300
  api_key=effective_api_key,
2301
  params={"limit": 1, "sort": "Newest", "period": "AllTime", "nsfw": "true"},
2302
  timeout=(7.0, 20.0),
 
2455
  return gr.update(value=probe_text, visible=True), gr.update(value=format_civitai_key_status_md(effective_api_key))
2456
 
2457
  def probe_civitai_api(query: str, api_key: str):
2458
+ page_origin = get_civitai_display_origin()
2459
  effective_api_key = get_effective_probe_api_key(api_key)
2460
  key_source = "input" if str(api_key or "").strip() else ("env" if effective_api_key else "none")
2461
  reset_civitai_key_status(effective_api_key, source="probe")
2462
  probe_query = str(query or "lora").strip() or "lora"
2463
  print(f"Civitai Probe: query={probe_query!r} key_source={key_source}")
2464
+ lines = ["### Civitai Probe", f"- page origin: {page_origin}", f"- query: {probe_query}", f"- key source: {key_source}"]
2465
  session = create_retry_session(total=4, backoff_factor=0.8)
2466
+ api_origin = resolve_civitai_api_origin(session)
2467
+ lines.insert(1, f"- api origin: {api_origin}")
2468
  anon_ok = False
2469
  try:
2470
+ page_resp = session.get(f"{page_origin}/models", params={"query": probe_query}, headers=get_civitai_headers(""), timeout=(7.0, 20.0))
2471
  lines.append(f"- anonymous page: {'ok' if page_resp.ok else 'fail'} ({page_resp.status_code})")
2472
  if page_resp.ok:
2473
  first_path = extract_first_model_path(page_resp.text)
2474
  if first_path:
2475
  lines.append(f"- anonymous first model: {first_path}")
2476
+ anon_resp = request_civitai_api(
2477
+ session,
2478
+ "/models",
2479
  params={"query": probe_query, "limit": 1, "sort": "Newest", "period": "AllTime", "nsfw": "true"},
 
2480
  timeout=(7.0, 20.0),
2481
+ label='Civitai anonymous probe models',
2482
+ source='probe-anon',
2483
  )
2484
+ anon_status = getattr(anon_resp, "status_code", "-")
2485
+ lines.append(f"- anonymous api: {'ok' if anon_resp and anon_resp.ok else 'fail'} ({anon_status})")
2486
+ anon_ok = bool(anon_resp is not None and anon_resp.ok)
2487
  except Exception as e:
2488
  lines.append(f"- anonymous probe: fail ({type(e).__name__}: {e})")
2489
+
2490
+ tags_probe = probe_json_api_step(
2491
+ session,
2492
+ "/tags",
2493
+ params={"limit": 1},
2494
+ timeout=(7.0, 20.0),
2495
+ label='Civitai probe tags',
2496
+ source='probe-tags',
2497
+ )
2498
+ if tags_probe["error"]:
2499
+ lines.append(f"- tags api: fail ({tags_probe['error']})")
2500
+ else:
2501
+ tags_state = 'ok' if tags_probe["ok"] and tags_probe["json_ok"] else 'fail'
2502
+ lines.append(f"- tags api: {tags_state} ({tags_probe['status']}, {tags_probe['host'] or '-'})")
2503
+
2504
+ creators_probe = probe_json_api_step(
2505
+ session,
2506
+ "/creators",
2507
+ api_key=effective_api_key,
2508
+ params={"limit": 1, "query": probe_query},
2509
+ timeout=(7.0, 20.0),
2510
+ label='Civitai probe creators',
2511
+ source='probe-creators',
2512
+ preferred_origin=CIVITAI_RED_ORIGIN,
2513
+ non_json_fallback_origin=CIVITAI_DEFAULT_ORIGIN,
2514
+ )
2515
+ if creators_probe["error"]:
2516
+ lines.append(f"- creators api: fail ({creators_probe['error']})")
2517
+ else:
2518
+ creators_state = 'ok' if creators_probe["ok"] and creators_probe["json_ok"] else 'fail'
2519
+ creator_host = canonicalize_civitai_host(creators_probe["host"]) if creators_probe["host"] else ''
2520
+ lines.append(f"- creators api: {creators_state} ({creators_probe['status']}, {creators_probe['host'] or '-'})")
2521
+ if creator_host == 'civitai.com':
2522
+ lines.append("- creators fallback: ok (civitai.com)")
2523
+ elif creator_host == 'civitai.red':
2524
+ lines.append("- creators fallback: not-needed (civitai.red json)")
2525
+
2526
  parsed_keys = parse_civitai_api_keys(effective_api_key)
2527
  if not parsed_keys:
2528
  lines.append("- auth resolve: skipped (no Civitai key)")
2529
+ lines.append("- direct download: skipped (no Civitai key)")
2530
  probe_text = "\n".join(lines)
2531
  print(probe_text)
2532
  return gr.update(value=probe_text, visible=True), gr.update(value=format_civitai_key_status_md(effective_api_key))
2533
+
2534
  try:
2535
  reset_civitai_key_status(effective_api_key, source="probe-auth")
2536
+ auth_resp = request_civitai_api(
2537
  session,
2538
+ "/models",
2539
  api_key=effective_api_key,
2540
  params={"query": probe_query, "limit": 1, "sort": "Newest", "period": "AllTime", "nsfw": "true"},
2541
  timeout=(7.0, 20.0),
2542
  label='Civitai probe models',
2543
  source='probe-auth',
2544
  )
2545
+ auth_status = getattr(auth_resp, "status_code", "-")
2546
+ lines.append(f"- auth api: {'ok' if auth_resp and auth_resp.ok else 'fail'} ({auth_status})")
2547
+ if auth_resp is not None and auth_resp.ok:
2548
+ payload = get_civitai_response_json(auth_resp, default={}) if auth_resp.content else {}
2549
  dl_url, model, version, file_info = extract_probe_first_download_url(payload)
2550
  if dl_url:
2551
  lines.append(f"- auth first file: {model.get('name', '')} / {version.get('name', '')} / {round(float(file_info.get('sizeKB', 0.0) or 0.0) / 1000.0, 2)}MB")
2552
+ model_url = build_civitai_model_url(model.get('id'), version.get('id'))
2553
+ normalized_model_download = normalize_civitai_input_url(model_url, api_key=effective_api_key)
2554
+ normalized_auth_download = normalize_civitai_download_api_url(dl_url)
2555
+ model_download_parts = get_civitai_url_parts(normalized_model_download)
2556
+ auth_download_parts = get_civitai_url_parts(normalized_auth_download)
2557
+ same_download_path = bool(
2558
+ is_civitai_download_api_path(model_download_parts.path)
2559
+ and is_civitai_download_api_path(auth_download_parts.path)
2560
+ and model_download_parts.path == auth_download_parts.path
2561
+ )
2562
+ lines.append(
2563
+ f"- model page normalize: {'ok' if same_download_path else 'mismatch'} ({sanitize_url_for_log(normalized_model_download)})"
2564
+ )
2565
  try:
2566
+ resolved_url = resolve_civitai_download_url(normalized_auth_download, effective_api_key, max_tries=1)
2567
  resolved_host = urllib.parse.urlparse(resolved_url).netloc
2568
  lines.append(f"- auth resolve: ok ({resolved_host})")
2569
+ download_probe = probe_download_url_step(session, resolved_url, api_key=effective_api_key, timeout=(7.0, 20.0))
2570
+ if download_probe["error"]:
2571
+ lines.append(f"- direct download: fail ({download_probe['error']})")
2572
+ else:
2573
+ lines.append(
2574
+ f"- direct download: {'ok' if download_probe['ok'] else 'fail'} ({download_probe['status']}, {download_probe['host'] or '-'})"
2575
+ )
2576
  except Exception as e:
2577
  lines.append(f"- auth resolve: fail ({type(e).__name__}: {e})")
2578
+ lines.append("- direct download: skipped (resolve failed)")
2579
  else:
2580
  lines.append("- auth resolve: skipped (no downloadUrl in first result)")
2581
+ lines.append("- direct download: skipped (no downloadUrl in first result)")
2582
  except Exception as e:
2583
  lines.append(f"- auth probe: fail ({type(e).__name__}: {e})")
2584
  if not anon_ok:
 
2586
  probe_text = "\n".join(lines)
2587
  print(probe_text)
2588
  return gr.update(value=probe_text, visible=True), gr.update(value=format_civitai_key_status_md(effective_api_key))