json_helpers does its own error handling and file locking, so the
try/except wrappers, threading locks and isfile checks were dead
weight; probe cache file paths are now defined in paths.py.
peek-header now returns the full probe (architecture fingerprint,
dtypes, quant scheme) alongside the metadata block, and caches the
response persistently by civitai file id; file content per id is
immutable, so cached probes skip the ranged fetch entirely.
The API carries no field distinguishing dual-transformer expert files;
conversion tools embed it in the header __metadata__. peek-header
range-fetches the JSON header and returns the metadata block without
downloading the file. Restricted to civitai.com urls.
Versions bundle companion files typed by the file, not the model;
'Text Encoder' had no TYPE_MAP entry so companions landed in the
checkpoint folder where the loader never scans.
Interrupted downloads leave url-hash .tmp resume files that nothing
cleans up; the queue that knows about them is in-memory only.
- iter_type_roots lists every folder downloads resolve into
- partials older than 7 days by mtime are deleted when the download
manager starts; active partials always have a fresh mtime
Multi-variant versions serve one canonical filename per file; the
precision, size class, format, and scan status that distinguish them
were dropped at the model layer.
- CivitFile gains metadata (format, size, fp), pickle and virus scan
results, and scannedAt
History entries stored only the query or tag term, so restoring a
search dropped every filter, and filter-only browsing was never
recorded at all.
- SearchHistory.add takes an optional params dict stored on the entry
- get_search records non-default filters: type, sort, period, base
models, nsfw, username, favorites
- filter-only searches are recorded as 'filter' entries labeled with
the filter values
A 200 reply to a resumed download carries the full file, not the
remainder; appending it to the partial produced a corrupt file that
passed the size check and only failed at hash verification. Truncate
the temp file and restart from byte 0 when a Range request comes back
as 200 instead of 206.
The /model-versions endpoint now serializes availability as null, which
failed str validation and turned every version lookup into a 404. Coerce
null to the default like the creator username validator already does.
Pass through earlyAccessEndsAt and earlyAccessConfig so clients can see
that a download requires a Buzz purchase before requesting the file.
CivitAI has no model type for a standalone transformer, so DiT finetunes
are published as 'Checkpoint' like full models and all landed in
Stable-diffusion, where the single-file transformer loader never looks.
Route Checkpoint downloads to UNET unless the base model is a known full
checkpoint (SD1.x/2.x, SDXL and its Pony/Illustrious/NoobAI derivatives,
SVD, Kolors, Stable Cascade). Downloads with no base_model, or a
civitai_save_type_folders override, keep the previous behavior.
An absolute 1024-byte floor rejected valid complete downloads, since
some safetensors are legitimately tiny (a lone projector-diff tensor is
~160 bytes). Verify completeness against the declared Content-Length
instead, accepting any size, and keep the floor only as a fallback when
it is missing. Reject text/* responses up front so an HTML error or
login page served with HTTP 200 is never saved as a model file.
The nsfw boolean leaks Mature+ content (nsfw=false returned levels up to
XXX) and /models rejects server-side level filtering, so filter search
results on each model's aggregate nsfwLevel: nsfw off keeps SFW (None +
Soft), nsfw on keeps everything.
Civitai returns a null username for deleted or anonymous creators. The
field default only applies when the key is absent, so an explicit null
failed str validation and rejected the entire CivitSearchResponse,
dropping every model on that page.
Coerce null or empty usernames to the default in a pre-validator so one
bad creator no longer empties the whole search result.
Replace base-model and model-type discovery that scraped ZodError
responses with the documented GET /api/v1/enums endpoint. The /images
baseModels probe (unreliable since /models stopped validating it) is now
a fallback only; sort and period still come from the /models 400 response
since /enums does not carry them.
Add the model fields the API returns but CivitModel was dropping:
supportsGeneration, mode, poi, minor, sfwOnly, userId, cosmetic. The
poi/minor/sfwOnly flags serialize as null on some endpoints, so coerce
null to False to keep parsing resilient.
Gate finds the on-disk preview via resolve_preview_file and a
non-empty UserComment check so previews lacking embedded params enter
the worker. embed_preview_parameters writes via temp + os.replace,
converts RGBA to RGB before JPEG save, removes unreadable sources, and
invalidates any co-located .thumb.jpg. get_exif decodes EXIF
UserComment so regenerated thumbs propagate the source's params. For
.mp4 and .webm previews, embed targets the .thumb.jpg frame extracted
by save_video_frame instead of the unembeddable video file.
backfill_preview_parameters and resolve_preview_file share
find_ui_preview_file, which mirrors find_preview's iteration order so
backfill embeds into the file the modernUI actually surfaces (handles
LoRAs with multiple coexisting variants like .jpg + .jpeg).
civitai_meta_to_parameters drops generator-specific noise keys (comfy,
comfyui, workflow, extraMetadata) and applies a 512-char cap on
pass-through values to keep the embedded chunk compact.
When download_civit_preview short-circuits at 304 (file exists), the
rescan path now falls back to backfill_preview_parameters: checks
whether the file already carries an embedded parameters chunk and, if
not, embeds img.meta in place. Existing libraries get retroactive
metadata via the existing "Search metadata" rescan, no model
re-download required.
Civitai's CDN strips PNG tEXt/EXIF chunks during delivery, leaving the
modernUI Preview metadata tab empty for downloaded networks. Format the
version API's images[].meta into sdnext's standard parameters channel
(PNG tEXt / JPEG-WEBP EXIF UserComment) so read_info_from_image picks
it up through the existing path. Embedding is wrapped in try/except
and never blocks the download.
CivitAI removed enum validation from /api/v1/models?baseModels=X (now
returns 200 with empty items on unknown values), which left the
base-model filter dropdown empty. Fix in two parts.
1. Point the base_models discovery probe at /images instead of /models.
/images still returns a ZodError with the full enum list (82 names
at time of writing). The existing parser handles the shape unchanged.
2. Also fetch civitai/civitai's base-model.constants.ts from GitHub and
merge per-name metadata (group, ecosystem, engine, family, hidden)
into a new base_models_info field on the response. The live probe
remains authoritative for which names exist; GitHub provides the
metadata and doubles as a fallback name list if the probe comes back
empty.
Backward compatible: the existing base_models list keeps the same
shape. Clients that want grouped dropdowns, hidden-flag filtering, or
image/video separation can consume base_models_info instead.
The GitHub fetch has a separate 6h cache since these constants change
much less often than the probes it sits alongside.
Civitai stopped validating baseModels on /api/v1/models (returns 200 with
empty items on unknown values), which left API clients without a way to
discover the values. Right now /images still does, I will get a separate
PR to put something more robust in place.
Add base_model, model_id, version_id params to download_civit_model.
Call resolve_save_path when subfolder sorting is enabled. Pass model_id
and version_id to DownloadManager.enqueue so _fetch_sidecar downloads
metadata JSON and preview images alongside the model file. Extract
version_id from the download URL as fallback when not provided.
Relax the empty-query guard so search works with tag, sort, or period
alone. Skip exact-match name filtering when no keyword is provided so
browse results return the full API response.