fix(tagging): skip a damaged WD14 label list in the preview (#431)

This commit is contained in:
Vito Sansevero
2026-10-31 17:16:23 -07:00
parent 66f8071198
commit 5981e1d9e3
4 changed files with 24 additions and 5 deletions
+6 -1
View File
@@ -44,6 +44,8 @@ BATCH_JOB_BODY_MAX = 32 * 1024
SKIPPED_PREVIEW_BODY_MAX = 64 * 1024
SKIPPED_PREVIEW_LEASE = "Skipped tags preview"
SKIPPED_PREVIEW_SAMPLE = 12
# WD14 label lists the preview found missing or unreadable (logged once each).
_MISSING_LABEL_LISTS = set()
REVIEW_REQUIRED = (
"Replacing tags needs a review of each image. Use Preview tagging review, "
"or choose Skip or Merge for a batch run."
@@ -760,8 +762,11 @@ class AutotagRoutesMixin:
try:
names = wd14_label_names(path)
except (OSError, ValueError) as exc:
self.logger.debug(f"No WD14 label list for {model_type}: {exc}")
if path not in _MISSING_LABEL_LISTS: # logged once per list
_MISSING_LABEL_LISTS.add(path)
self.logger.debug(f"No WD14 label list for {model_type}: {exc}")
continue
_MISSING_LABEL_LISTS.discard(path)
lists.append(MODELS[model_type]["name"])
for name in names:
known.setdefault(name.casefold(), (1, name))
+7 -3
View File
@@ -204,8 +204,8 @@ def read_wd14_label_rows(path) -> List[Dict[str, str]]:
def wd14_label_names(path) -> Tuple[str, ...]:
"""The names a WD14 label list can suggest (categories 0 and 4), cached
by ``(mtime_ns, size)``. OSError and ValueError propagate (the caller
skips a missing or unreadable list)."""
by ``(mtime_ns, size)``. OSError (missing) and ValueError (unreadable)
propagate; the caller skips that list."""
key = os.fspath(path)
stat = os.stat(key)
stamp = (stat.st_mtime_ns, stat.st_size)
@@ -213,9 +213,13 @@ def wd14_label_names(path) -> Tuple[str, ...]:
cached = _label_cache.get(key)
if cached is not None and cached[0] == stamp:
return cached[1]
try:
rows = read_wd14_label_rows(key)
except csv.Error as exc: # a damaged list (a NUL byte, an oversized field)
raise ValueError(f"unreadable label list: {exc}") from None
names = tuple(
name
for row in read_wd14_label_rows(key)
for row in rows
if row.get("category", "0") in WD14_SUGGESTED_CATEGORIES
and (name := (row.get("name") or "").strip())
)
+10
View File
@@ -5,6 +5,7 @@ reader and the filter in ``AutoTagService.generate_tag_suggestions``. No model
is loaded: ``_scored_tags`` is replaced by a fixed list.
"""
import csv
import os
import tempfile
import unicodedata
@@ -190,6 +191,15 @@ class TestWd14Labels(unittest.TestCase):
self.assertEqual(wd14_label_names(self.path), ("blurry", "sky"))
self.assertEqual(reader.call_count, 2)
def test_a_damaged_list_is_a_value_error_for_the_caller_to_skip(self):
# A field over the csv module's size limit raises csv.Error.
big = "x" * (csv.field_size_limit() + 1)
self.path.write_text(
f"tag_id,name,category,count\n1,{big},0,1\n", encoding="utf-8"
)
with self.assertRaises(ValueError):
wd14_label_names(self.path)
def test_a_missing_list_raises_for_the_caller_to_skip(self):
with self.assertRaises(OSError):
wd14_label_names(self.path)
+1 -1
View File
@@ -107,7 +107,6 @@ export function skippedTagsBlock({ api, scope, drafts, delay = PREVIEW_DELAY_MS
input.id = 'tag-skipped-input';
input.autocomplete = 'off';
input.placeholder = SKIPPED_COPY.placeholder;
input.maxLength = MAX_ENTRY_CHARS + 10;
input.setAttribute('aria-describedby', 'tag-skipped-problem');
const add = createButton(SKIPPED_COPY.add);
add.id = 'tag-skipped-add';
@@ -116,6 +115,7 @@ export function skippedTagsBlock({ api, scope, drafts, delay = PREVIEW_DELAY_MS
problem.id = 'tag-skipped-problem';
problem.setAttribute('role', 'alert');
const chips = el('div', undefined, 'tags skip-chips');
chips.setAttribute('role', 'group');
chips.setAttribute('aria-label', SKIPPED_COPY.chips);
const preview = el('p', '', 'small skip-preview');
preview.setAttribute('role', 'status');