CivitAI video covers are AV1, which OpenCV builds without dav1d cannot
decode. Previews fetch the CDN's transcode=true,width=450 H.264 copy
first, fall back to the original named by its content type, and drop a
video that yields no frame for the next media item. An unreadable frame
logs one line, and the download path is absolute before registration.
A refused download recorded only the status code and logged nothing.
The error now carries CivitAI's own message, as in "HTTP 403: Early
Access: ...", and the refusal and rename failure log like every other
branch.
A finished download refreshed the checkpoint list alone, so a new LoRA
or UNET stayed invisible to check-local and Networks until a manual
refresh. add_network, now module-level, registers one LoRA file; unet,
vae and checkpoint downloads run their loader's scan.
A hash cache hit now resolves through its loader's registry and counts
only while the file exists; entries whose file is gone are pruned, on
the request that finds them and once per process for the rest.
filename is always the local path.
CivitSearchResponse carries the server's error text; the Gradio page
renders it, or a no-results line, in the details area instead of a
blank card list. The legacy route reads items from the response.
search_civitai.base_models was a hand-kept copy of the /enums list that
lacked every base added this year. The dropdown starts empty and takes
the discovered names minus the bases CivitAI has retired (in BaseModel
but not ActiveBaseModel). The tab handler refreshes the choice lists on
every visit, since later browser sessions used to get no update; the
initial search still runs once per process.
The versions table showed only files[0], so precision variants of one
version could not be downloaded. Each file gets its own row under
rowspan version cells, sorted by precision then size, with a variant
column and a star on the primary; rows download by fileId URL and All
variants queues each version's primary from the fetched model. Companion
files pass their own type, and save names carry the precision, then
full/pruned and the file id only when needed to stay unique.
The downloader hashes bytes as it writes them, seeding from the partial
on resume, and checks the declared SHA256 against that digest instead
of rereading the file. The declared hash, or the computed one when none
is given, is cached under the key the owning loader reads; loader_kind
and hash_cache_title derive that key for the downloader and the sweeps.
resolve_save_path passed os.sep as the re.sub replacement string; on
Windows that lone backslash is an unfinished escape and every subfolder
download raised PatternError. A callable replacement is inserted
verbatim.
An overloaded text search answers 503 with Retry-After, which send now
retries like a 429. Failure logs carry the server's error text with
ZodError issues flattened; a 404 from by-hash and a 401 from /me stay
quiet.
The sweeps queried with the extra-networks shorthash, which for LoRA is
the kohya sshs_model_hash and never matches, and the update sweep
skipped every uncached checkpoint. Hashes now come from the default
hash store or from hashing the file, lookups are batched, hashing stops
on interrupt and honours --no-hashing, and one lock serializes both
sweeps with a second start answered by 409.
Every API call goes through CivitaiClient.send, which caps concurrent
requests at shared.max_workers and retries 429 up to four times, using
Retry-After or exponential backoff up to 60s. New lookups wrap POST
/model-versions/by-hash/ids, POST /model-versions/by-hash and GET
/models?ids= within their caps and return a status per input whose chunk
failed; the ids query sends nsfw=true because it drops NSFW models
otherwise.
GET /sdapi/v2/civitai/version/mini/{id} exposes /model-versions/mini:
the primary file flattened onto the version, every hash, and the
permission flags a download needs. CivitVersionMini is its own model
because its field set is per-file, not per-version.
favoriteCount, ratingCount and rating are gone since reactions replaced
ratings, tippedAmountCount is new, and unreported counts arrive as null,
which a strict int field turned into a rejected search page. /me lost
its avatar and reports tier, status and isMember, with tier absent for
non-members; email, emailVerified and tokenScope stay unmodelled.
Eight live ModelType values had no TYPE_MAP entry and fell into the
checkpoint folder, where a detection or encoder model shows up in the
model picker. TextEncoder, UNet, CLIP, CLIPVision and Detection get
their own folders; LLM, VisionLanguage and ComfyWorkflows have none and
are logged once per session.
The civitai repo dropped base-model.constants.ts, so the GitHub fetch
404ed and every call lost the group and hidden flags. /enums carries
ActiveBaseModel next to BaseModel; the difference is the retired set the
hidden flag marked.
post_settings wrote opts.data directly, bypassing the __setattr__ hook
that files _token keys into secrets.json; a token saved through the
endpoint landed in config.json. The setter also applies the freeze and
restricted-key guards.
save_cache handed writefile the live HashStore objects, so an add_hash
from another thread during the deep copy raised "dictionary changed
size during iteration" and dropped the save. The stores are now copied
under a lock that also covers the write, so a later save never lands
under an earlier snapshot.
Every caller writes a whole JSON document, and a plain open/write can
leave a torn or empty file when the process dies or another writer
overlaps, which readfile then returns as {}. Writes now go through the
temp file and replace unless atomic=False or the mode is append. A read
waits out an open that Windows refuses while a replace of the same name
is in flight, and an empty file is reported when the read is not
silent, since a zero-byte config.json otherwise resets settings without
a trace.
The fasteners lock is inter-process only. On Linux fcntl record locks
belong to the process, so two threads writing the same file were never
serialized and could tear it; on Windows they were, until the lock
switched itself off. Every writer of these files runs inside one
multi-threaded process, so that is the case that matters.
readfile(lock=True) and writefile now take a re-entrant lock per
normalized path, held from the snapshot through the replace so writes
to one path land in call order. Other processes are covered by the
atomic replace. A .lock file left next to a JSON file by the old lock
is removed the first time that path is used.
writefile(atomic=True) renamed the temp file while it was still open,
which Windows refuses, so every atomic save failed there and left a temp
file behind. The temp file is now created with mkstemp, closed before
os.replace and removed when the save fails; the replace retries briefly
when another handle holds the target, which Windows reports as a
permission error.
Both helpers removed the .lock file after releasing it. That removal
raced other holders on both platforms, and any failure switched locking
off for the whole process without a log line. The lock file now stays in
place; a failed release or a lock timeout is logged instead.
writefile deep-copied and validated the live object in Python, so a
concurrent insert into a shared dict raised "dictionary changed size
during iteration" and dropped the save. dict.copy and list.copy run
under the GIL, so the deep copy and the per-key validation now walk that
snapshot; nested containers remain the caller's responsibility.
test/test-json-helpers.py covers all three on Linux and Windows.
get_cu_seqlens creates a cu_seqlens tensor with device="cuda",
which crashes on non-CUDA devices (e.g. Ascend NPU raises
"Torch not compiled with CUDA enabled"). Create it on the same
device as the input text_mask instead, which is device-agnostic.