mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-26 07:35:44 -03:00
feat: add configurable mature blur threshold setting
Add new setting 'mature_blur_level' with options PG13/R/X/XXX to control which NSFW rating level triggers blur filtering when NSFW blur is enabled. - Backend: update preview selection logic to respect threshold - Frontend: update UI components to use configurable threshold - Settings: add validation and normalization for mature_blur_level - Tests: add coverage for new threshold behavior - Translations: add keys for all supported languages Fixes #867
This commit is contained in:
@@ -179,6 +179,8 @@ Insomnia Art Designs, megakirbs, Brennok, wackop, 2018cfh, Takkan, stone9k, $Met
|
|||||||
- Context menu for quick actions
|
- Context menu for quick actions
|
||||||
- Custom notes and usage tips
|
- Custom notes and usage tips
|
||||||
- Multi-folder support
|
- Multi-folder support
|
||||||
|
- Configurable mature blur threshold (`PG13` / `R` / `X` / `XXX`, default `R+`)
|
||||||
|
- Example: setting threshold to `PG13` blurs `PG13`, `R`, `X`, and `XXX` previews when blur is enabled
|
||||||
- Visual progress indicators during initialization
|
- Visual progress indicators during initialization
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -291,7 +291,15 @@
|
|||||||
"blurNsfwContent": "NSFW-Inhalte unscharf stellen",
|
"blurNsfwContent": "NSFW-Inhalte unscharf stellen",
|
||||||
"blurNsfwContentHelp": "Nicht jugendfreie (NSFW) Vorschaubilder unscharf stellen",
|
"blurNsfwContentHelp": "Nicht jugendfreie (NSFW) Vorschaubilder unscharf stellen",
|
||||||
"showOnlySfw": "Nur SFW-Ergebnisse anzeigen",
|
"showOnlySfw": "Nur SFW-Ergebnisse anzeigen",
|
||||||
"showOnlySfwHelp": "Alle NSFW-Inhalte beim Durchsuchen und Suchen herausfiltern"
|
"showOnlySfwHelp": "Alle NSFW-Inhalte beim Durchsuchen und Suchen herausfiltern",
|
||||||
|
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||||
|
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||||
|
"matureBlurThresholdOptions": {
|
||||||
|
"pg13": "[TODO: Translate] PG13 and above",
|
||||||
|
"r": "[TODO: Translate] R and above (default)",
|
||||||
|
"x": "[TODO: Translate] X and above",
|
||||||
|
"xxx": "[TODO: Translate] XXX only"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"videoSettings": {
|
"videoSettings": {
|
||||||
"autoplayOnHover": "Videos bei Hover automatisch abspielen",
|
"autoplayOnHover": "Videos bei Hover automatisch abspielen",
|
||||||
|
|||||||
@@ -291,7 +291,15 @@
|
|||||||
"blurNsfwContent": "Blur NSFW Content",
|
"blurNsfwContent": "Blur NSFW Content",
|
||||||
"blurNsfwContentHelp": "Blur mature (NSFW) content preview images",
|
"blurNsfwContentHelp": "Blur mature (NSFW) content preview images",
|
||||||
"showOnlySfw": "Show Only SFW Results",
|
"showOnlySfw": "Show Only SFW Results",
|
||||||
"showOnlySfwHelp": "Filter out all NSFW content when browsing and searching"
|
"showOnlySfwHelp": "Filter out all NSFW content when browsing and searching",
|
||||||
|
"matureBlurThreshold": "Mature Blur Threshold",
|
||||||
|
"matureBlurThresholdHelp": "Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||||
|
"matureBlurThresholdOptions": {
|
||||||
|
"pg13": "PG13 and above",
|
||||||
|
"r": "R and above (default)",
|
||||||
|
"x": "X and above",
|
||||||
|
"xxx": "XXX only"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"videoSettings": {
|
"videoSettings": {
|
||||||
"autoplayOnHover": "Autoplay Videos on Hover",
|
"autoplayOnHover": "Autoplay Videos on Hover",
|
||||||
@@ -1758,4 +1766,4 @@
|
|||||||
"retry": "Retry"
|
"retry": "Retry"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,7 +291,15 @@
|
|||||||
"blurNsfwContent": "Difuminar contenido NSFW",
|
"blurNsfwContent": "Difuminar contenido NSFW",
|
||||||
"blurNsfwContentHelp": "Difuminar imágenes de vista previa de contenido para adultos (NSFW)",
|
"blurNsfwContentHelp": "Difuminar imágenes de vista previa de contenido para adultos (NSFW)",
|
||||||
"showOnlySfw": "Mostrar solo resultados SFW",
|
"showOnlySfw": "Mostrar solo resultados SFW",
|
||||||
"showOnlySfwHelp": "Filtrar todo el contenido NSFW al navegar y buscar"
|
"showOnlySfwHelp": "Filtrar todo el contenido NSFW al navegar y buscar",
|
||||||
|
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||||
|
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||||
|
"matureBlurThresholdOptions": {
|
||||||
|
"pg13": "[TODO: Translate] PG13 and above",
|
||||||
|
"r": "[TODO: Translate] R and above (default)",
|
||||||
|
"x": "[TODO: Translate] X and above",
|
||||||
|
"xxx": "[TODO: Translate] XXX only"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"videoSettings": {
|
"videoSettings": {
|
||||||
"autoplayOnHover": "Reproducir videos automáticamente al pasar el ratón",
|
"autoplayOnHover": "Reproducir videos automáticamente al pasar el ratón",
|
||||||
|
|||||||
@@ -291,7 +291,15 @@
|
|||||||
"blurNsfwContent": "Flouter le contenu NSFW",
|
"blurNsfwContent": "Flouter le contenu NSFW",
|
||||||
"blurNsfwContentHelp": "Flouter les images d'aperçu de contenu pour adultes (NSFW)",
|
"blurNsfwContentHelp": "Flouter les images d'aperçu de contenu pour adultes (NSFW)",
|
||||||
"showOnlySfw": "Afficher uniquement les résultats SFW",
|
"showOnlySfw": "Afficher uniquement les résultats SFW",
|
||||||
"showOnlySfwHelp": "Filtrer tout le contenu NSFW lors de la navigation et de la recherche"
|
"showOnlySfwHelp": "Filtrer tout le contenu NSFW lors de la navigation et de la recherche",
|
||||||
|
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||||
|
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||||
|
"matureBlurThresholdOptions": {
|
||||||
|
"pg13": "[TODO: Translate] PG13 and above",
|
||||||
|
"r": "[TODO: Translate] R and above (default)",
|
||||||
|
"x": "[TODO: Translate] X and above",
|
||||||
|
"xxx": "[TODO: Translate] XXX only"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"videoSettings": {
|
"videoSettings": {
|
||||||
"autoplayOnHover": "Lecture automatique vidéo au survol",
|
"autoplayOnHover": "Lecture automatique vidéo au survol",
|
||||||
|
|||||||
@@ -291,7 +291,15 @@
|
|||||||
"blurNsfwContent": "טשטש תוכן NSFW",
|
"blurNsfwContent": "טשטש תוכן NSFW",
|
||||||
"blurNsfwContentHelp": "טשטש תמונות תצוגה מקדימה של תוכן למבוגרים (NSFW)",
|
"blurNsfwContentHelp": "טשטש תמונות תצוגה מקדימה של תוכן למבוגרים (NSFW)",
|
||||||
"showOnlySfw": "הצג רק תוצאות SFW",
|
"showOnlySfw": "הצג רק תוצאות SFW",
|
||||||
"showOnlySfwHelp": "סנן את כל התוכן ה-NSFW בעת גלישה וחיפוש"
|
"showOnlySfwHelp": "סנן את כל התוכן ה-NSFW בעת גלישה וחיפוש",
|
||||||
|
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||||
|
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||||
|
"matureBlurThresholdOptions": {
|
||||||
|
"pg13": "[TODO: Translate] PG13 and above",
|
||||||
|
"r": "[TODO: Translate] R and above (default)",
|
||||||
|
"x": "[TODO: Translate] X and above",
|
||||||
|
"xxx": "[TODO: Translate] XXX only"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"videoSettings": {
|
"videoSettings": {
|
||||||
"autoplayOnHover": "נגן וידאו אוטומטית בריחוף",
|
"autoplayOnHover": "נגן וידאו אוטומטית בריחוף",
|
||||||
|
|||||||
@@ -291,7 +291,15 @@
|
|||||||
"blurNsfwContent": "NSFWコンテンツをぼかす",
|
"blurNsfwContent": "NSFWコンテンツをぼかす",
|
||||||
"blurNsfwContentHelp": "成人向け(NSFW)コンテンツのプレビュー画像をぼかします",
|
"blurNsfwContentHelp": "成人向け(NSFW)コンテンツのプレビュー画像をぼかします",
|
||||||
"showOnlySfw": "SFWコンテンツのみ表示",
|
"showOnlySfw": "SFWコンテンツのみ表示",
|
||||||
"showOnlySfwHelp": "閲覧と検索時にすべてのNSFWコンテンツを除外します"
|
"showOnlySfwHelp": "閲覧と検索時にすべてのNSFWコンテンツを除外します",
|
||||||
|
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||||
|
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||||
|
"matureBlurThresholdOptions": {
|
||||||
|
"pg13": "[TODO: Translate] PG13 and above",
|
||||||
|
"r": "[TODO: Translate] R and above (default)",
|
||||||
|
"x": "[TODO: Translate] X and above",
|
||||||
|
"xxx": "[TODO: Translate] XXX only"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"videoSettings": {
|
"videoSettings": {
|
||||||
"autoplayOnHover": "ホバー時に動画を自動再生",
|
"autoplayOnHover": "ホバー時に動画を自動再生",
|
||||||
|
|||||||
@@ -291,7 +291,15 @@
|
|||||||
"blurNsfwContent": "NSFW 콘텐츠 블러 처리",
|
"blurNsfwContent": "NSFW 콘텐츠 블러 처리",
|
||||||
"blurNsfwContentHelp": "성인(NSFW) 콘텐츠 미리보기 이미지를 블러 처리합니다",
|
"blurNsfwContentHelp": "성인(NSFW) 콘텐츠 미리보기 이미지를 블러 처리합니다",
|
||||||
"showOnlySfw": "SFW 결과만 표시",
|
"showOnlySfw": "SFW 결과만 표시",
|
||||||
"showOnlySfwHelp": "탐색 및 검색 시 모든 NSFW 콘텐츠를 필터링합니다"
|
"showOnlySfwHelp": "탐색 및 검색 시 모든 NSFW 콘텐츠를 필터링합니다",
|
||||||
|
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||||
|
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||||
|
"matureBlurThresholdOptions": {
|
||||||
|
"pg13": "[TODO: Translate] PG13 and above",
|
||||||
|
"r": "[TODO: Translate] R and above (default)",
|
||||||
|
"x": "[TODO: Translate] X and above",
|
||||||
|
"xxx": "[TODO: Translate] XXX only"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"videoSettings": {
|
"videoSettings": {
|
||||||
"autoplayOnHover": "호버 시 비디오 자동 재생",
|
"autoplayOnHover": "호버 시 비디오 자동 재생",
|
||||||
|
|||||||
@@ -291,7 +291,15 @@
|
|||||||
"blurNsfwContent": "Размывать NSFW контент",
|
"blurNsfwContent": "Размывать NSFW контент",
|
||||||
"blurNsfwContentHelp": "Размывать превью изображений контента для взрослых (NSFW)",
|
"blurNsfwContentHelp": "Размывать превью изображений контента для взрослых (NSFW)",
|
||||||
"showOnlySfw": "Показывать только SFW результаты",
|
"showOnlySfw": "Показывать только SFW результаты",
|
||||||
"showOnlySfwHelp": "Фильтровать весь NSFW контент при просмотре и поиске"
|
"showOnlySfwHelp": "Фильтровать весь NSFW контент при просмотре и поиске",
|
||||||
|
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||||
|
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||||
|
"matureBlurThresholdOptions": {
|
||||||
|
"pg13": "[TODO: Translate] PG13 and above",
|
||||||
|
"r": "[TODO: Translate] R and above (default)",
|
||||||
|
"x": "[TODO: Translate] X and above",
|
||||||
|
"xxx": "[TODO: Translate] XXX only"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"videoSettings": {
|
"videoSettings": {
|
||||||
"autoplayOnHover": "Автовоспроизведение видео при наведении",
|
"autoplayOnHover": "Автовоспроизведение видео при наведении",
|
||||||
|
|||||||
@@ -291,7 +291,15 @@
|
|||||||
"blurNsfwContent": "模糊 NSFW 内容",
|
"blurNsfwContent": "模糊 NSFW 内容",
|
||||||
"blurNsfwContentHelp": "模糊成熟(NSFW)内容预览图片",
|
"blurNsfwContentHelp": "模糊成熟(NSFW)内容预览图片",
|
||||||
"showOnlySfw": "仅显示 SFW 结果",
|
"showOnlySfw": "仅显示 SFW 结果",
|
||||||
"showOnlySfwHelp": "浏览和搜索时过滤所有 NSFW 内容"
|
"showOnlySfwHelp": "浏览和搜索时过滤所有 NSFW 内容",
|
||||||
|
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||||
|
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||||
|
"matureBlurThresholdOptions": {
|
||||||
|
"pg13": "[TODO: Translate] PG13 and above",
|
||||||
|
"r": "[TODO: Translate] R and above (default)",
|
||||||
|
"x": "[TODO: Translate] X and above",
|
||||||
|
"xxx": "[TODO: Translate] XXX only"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"videoSettings": {
|
"videoSettings": {
|
||||||
"autoplayOnHover": "悬停时自动播放视频",
|
"autoplayOnHover": "悬停时自动播放视频",
|
||||||
|
|||||||
@@ -291,7 +291,15 @@
|
|||||||
"blurNsfwContent": "模糊 NSFW 內容",
|
"blurNsfwContent": "模糊 NSFW 內容",
|
||||||
"blurNsfwContentHelp": "模糊成熟(NSFW)內容預覽圖片",
|
"blurNsfwContentHelp": "模糊成熟(NSFW)內容預覽圖片",
|
||||||
"showOnlySfw": "僅顯示 SFW 結果",
|
"showOnlySfw": "僅顯示 SFW 結果",
|
||||||
"showOnlySfwHelp": "瀏覽和搜尋時過濾所有 NSFW 內容"
|
"showOnlySfwHelp": "瀏覽和搜尋時過濾所有 NSFW 內容",
|
||||||
|
"matureBlurThreshold": "[TODO: Translate] Mature Blur Threshold",
|
||||||
|
"matureBlurThresholdHelp": "[TODO: Translate] Set which rating level starts blur filtering when NSFW blur is enabled.",
|
||||||
|
"matureBlurThresholdOptions": {
|
||||||
|
"pg13": "[TODO: Translate] PG13 and above",
|
||||||
|
"r": "[TODO: Translate] R and above (default)",
|
||||||
|
"x": "[TODO: Translate] X and above",
|
||||||
|
"xxx": "[TODO: Translate] XXX only"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"videoSettings": {
|
"videoSettings": {
|
||||||
"autoplayOnHover": "滑鼠懸停自動播放影片",
|
"autoplayOnHover": "滑鼠懸停自動播放影片",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from ..utils.constants import (
|
|||||||
VALID_LORA_TYPES,
|
VALID_LORA_TYPES,
|
||||||
)
|
)
|
||||||
from ..utils.civitai_utils import rewrite_preview_url
|
from ..utils.civitai_utils import rewrite_preview_url
|
||||||
from ..utils.preview_selection import select_preview_media
|
from ..utils.preview_selection import resolve_mature_threshold, select_preview_media
|
||||||
from ..utils.utils import sanitize_folder_name
|
from ..utils.utils import sanitize_folder_name
|
||||||
from ..utils.exif_utils import ExifUtils
|
from ..utils.exif_utils import ExifUtils
|
||||||
from ..utils.metadata_manager import MetadataManager
|
from ..utils.metadata_manager import MetadataManager
|
||||||
@@ -846,9 +846,13 @@ class DownloadManager:
|
|||||||
blur_mature_content = bool(
|
blur_mature_content = bool(
|
||||||
settings_manager.get("blur_mature_content", True)
|
settings_manager.get("blur_mature_content", True)
|
||||||
)
|
)
|
||||||
|
mature_threshold = resolve_mature_threshold(
|
||||||
|
{"mature_blur_level": settings_manager.get("mature_blur_level", "R")}
|
||||||
|
)
|
||||||
selected_image, nsfw_level = select_preview_media(
|
selected_image, nsfw_level = select_preview_media(
|
||||||
images,
|
images,
|
||||||
blur_mature_content=blur_mature_content,
|
blur_mature_content=blur_mature_content,
|
||||||
|
mature_threshold=mature_threshold,
|
||||||
)
|
)
|
||||||
|
|
||||||
preview_url = selected_image.get("url") if selected_image else None
|
preview_url = selected_image.get("url") if selected_image else None
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from typing import Any, Dict, Iterable, List, Mapping, Optional, Sequence
|
|||||||
from .errors import RateLimitError, ResourceNotFoundError
|
from .errors import RateLimitError, ResourceNotFoundError
|
||||||
from .settings_manager import get_settings_manager
|
from .settings_manager import get_settings_manager
|
||||||
from ..utils.civitai_utils import rewrite_preview_url
|
from ..utils.civitai_utils import rewrite_preview_url
|
||||||
from ..utils.preview_selection import select_preview_media
|
from ..utils.preview_selection import resolve_mature_threshold, select_preview_media
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -1252,14 +1252,23 @@ class ModelUpdateService:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
blur_mature_content = True
|
blur_mature_content = True
|
||||||
|
mature_threshold = resolve_mature_threshold({"mature_blur_level": "R"})
|
||||||
settings = getattr(self, "_settings", None)
|
settings = getattr(self, "_settings", None)
|
||||||
if settings is not None and hasattr(settings, "get"):
|
if settings is not None and hasattr(settings, "get"):
|
||||||
try:
|
try:
|
||||||
blur_mature_content = bool(settings.get("blur_mature_content", True))
|
blur_mature_content = bool(settings.get("blur_mature_content", True))
|
||||||
|
mature_threshold = resolve_mature_threshold(
|
||||||
|
{"mature_blur_level": settings.get("mature_blur_level", "R")}
|
||||||
|
)
|
||||||
except Exception: # pragma: no cover - defensive guard
|
except Exception: # pragma: no cover - defensive guard
|
||||||
blur_mature_content = True
|
blur_mature_content = True
|
||||||
|
mature_threshold = resolve_mature_threshold({"mature_blur_level": "R"})
|
||||||
|
|
||||||
selected, _ = select_preview_media(candidates, blur_mature_content=blur_mature_content)
|
selected, _ = select_preview_media(
|
||||||
|
candidates,
|
||||||
|
blur_mature_content=blur_mature_content,
|
||||||
|
mature_threshold=mature_threshold,
|
||||||
|
)
|
||||||
if not selected:
|
if not selected:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from urllib.parse import urlparse
|
|||||||
|
|
||||||
from ..utils.constants import CARD_PREVIEW_WIDTH, PREVIEW_EXTENSIONS
|
from ..utils.constants import CARD_PREVIEW_WIDTH, PREVIEW_EXTENSIONS
|
||||||
from ..utils.civitai_utils import rewrite_preview_url
|
from ..utils.civitai_utils import rewrite_preview_url
|
||||||
from ..utils.preview_selection import select_preview_media
|
from ..utils.preview_selection import resolve_mature_threshold, select_preview_media
|
||||||
from .settings_manager import get_settings_manager
|
from .settings_manager import get_settings_manager
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -49,9 +49,13 @@ class PreviewAssetService:
|
|||||||
blur_mature_content = bool(
|
blur_mature_content = bool(
|
||||||
settings_manager.get("blur_mature_content", True)
|
settings_manager.get("blur_mature_content", True)
|
||||||
)
|
)
|
||||||
|
mature_threshold = resolve_mature_threshold(
|
||||||
|
{"mature_blur_level": settings_manager.get("mature_blur_level", "R")}
|
||||||
|
)
|
||||||
first_preview, nsfw_level = select_preview_media(
|
first_preview, nsfw_level = select_preview_media(
|
||||||
images,
|
images,
|
||||||
blur_mature_content=blur_mature_content,
|
blur_mature_content=blur_mature_content,
|
||||||
|
mature_threshold=mature_threshold,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not first_preview:
|
if not first_preview:
|
||||||
@@ -216,4 +220,3 @@ class PreviewAssetService:
|
|||||||
if "webm" in content_type:
|
if "webm" in content_type:
|
||||||
return ".webm"
|
return ".webm"
|
||||||
return ".mp4"
|
return ".mp4"
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from typing import Any, Awaitable, Dict, Iterable, List, Mapping, Optional, Sequ
|
|||||||
from platformdirs import user_config_dir
|
from platformdirs import user_config_dir
|
||||||
|
|
||||||
from ..utils.constants import DEFAULT_HASH_CHUNK_SIZE_MB, DEFAULT_PRIORITY_TAG_CONFIG
|
from ..utils.constants import DEFAULT_HASH_CHUNK_SIZE_MB, DEFAULT_PRIORITY_TAG_CONFIG
|
||||||
|
from ..utils.preview_selection import VALID_MATURE_BLUR_LEVELS
|
||||||
from ..utils.settings_paths import APP_NAME, ensure_settings_file, get_legacy_settings_path
|
from ..utils.settings_paths import APP_NAME, ensure_settings_file, get_legacy_settings_path
|
||||||
from ..utils.tag_priorities import (
|
from ..utils.tag_priorities import (
|
||||||
PriorityTagEntry,
|
PriorityTagEntry,
|
||||||
@@ -59,6 +60,7 @@ DEFAULT_SETTINGS: Dict[str, Any] = {
|
|||||||
"optimize_example_images": True,
|
"optimize_example_images": True,
|
||||||
"auto_download_example_images": False,
|
"auto_download_example_images": False,
|
||||||
"blur_mature_content": True,
|
"blur_mature_content": True,
|
||||||
|
"mature_blur_level": "R",
|
||||||
"autoplay_on_hover": False,
|
"autoplay_on_hover": False,
|
||||||
"display_density": "default",
|
"display_density": "default",
|
||||||
"card_info_display": "always",
|
"card_info_display": "always",
|
||||||
@@ -274,6 +276,16 @@ class SettingsManager:
|
|||||||
self.settings["metadata_refresh_skip_paths"] = []
|
self.settings["metadata_refresh_skip_paths"] = []
|
||||||
inserted_defaults = True
|
inserted_defaults = True
|
||||||
|
|
||||||
|
had_mature_level = "mature_blur_level" in self.settings
|
||||||
|
raw_mature_level = self.settings.get("mature_blur_level")
|
||||||
|
normalized_mature_level = self.normalize_mature_blur_level(raw_mature_level)
|
||||||
|
if normalized_mature_level != raw_mature_level:
|
||||||
|
self.settings["mature_blur_level"] = normalized_mature_level
|
||||||
|
if had_mature_level:
|
||||||
|
updated_existing = True
|
||||||
|
else:
|
||||||
|
inserted_defaults = True
|
||||||
|
|
||||||
for key, value in defaults.items():
|
for key, value in defaults.items():
|
||||||
if key == "priority_tags":
|
if key == "priority_tags":
|
||||||
continue
|
continue
|
||||||
@@ -608,6 +620,7 @@ class SettingsManager:
|
|||||||
'optimizeExampleImages': 'optimize_example_images',
|
'optimizeExampleImages': 'optimize_example_images',
|
||||||
'autoDownloadExampleImages': 'auto_download_example_images',
|
'autoDownloadExampleImages': 'auto_download_example_images',
|
||||||
'blurMatureContent': 'blur_mature_content',
|
'blurMatureContent': 'blur_mature_content',
|
||||||
|
'matureBlurLevel': 'mature_blur_level',
|
||||||
'autoplayOnHover': 'autoplay_on_hover',
|
'autoplayOnHover': 'autoplay_on_hover',
|
||||||
'displayDensity': 'display_density',
|
'displayDensity': 'display_density',
|
||||||
'cardInfoDisplay': 'card_info_display',
|
'cardInfoDisplay': 'card_info_display',
|
||||||
@@ -860,6 +873,13 @@ class SettingsManager:
|
|||||||
|
|
||||||
return normalized
|
return normalized
|
||||||
|
|
||||||
|
def normalize_mature_blur_level(self, value: Any) -> str:
|
||||||
|
if isinstance(value, str):
|
||||||
|
normalized = value.strip().upper()
|
||||||
|
if normalized in VALID_MATURE_BLUR_LEVELS:
|
||||||
|
return normalized
|
||||||
|
return "R"
|
||||||
|
|
||||||
def normalize_auto_organize_exclusions(self, value: Any) -> List[str]:
|
def normalize_auto_organize_exclusions(self, value: Any) -> List[str]:
|
||||||
if value is None:
|
if value is None:
|
||||||
return []
|
return []
|
||||||
@@ -1012,6 +1032,8 @@ class SettingsManager:
|
|||||||
value = self.normalize_auto_organize_exclusions(value)
|
value = self.normalize_auto_organize_exclusions(value)
|
||||||
elif key == "metadata_refresh_skip_paths":
|
elif key == "metadata_refresh_skip_paths":
|
||||||
value = self.normalize_metadata_refresh_skip_paths(value)
|
value = self.normalize_metadata_refresh_skip_paths(value)
|
||||||
|
elif key == "mature_blur_level":
|
||||||
|
value = self.normalize_mature_blur_level(value)
|
||||||
self.settings[key] = value
|
self.settings[key] = value
|
||||||
portable_switch_pending = False
|
portable_switch_pending = False
|
||||||
if key == "use_portable_settings" and isinstance(value, bool):
|
if key == "use_portable_settings" and isinstance(value, bool):
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Mapping, Optional, Sequence, Tuple
|
from typing import Any, Mapping, Optional, Sequence, Tuple
|
||||||
|
|
||||||
from .constants import NSFW_LEVELS
|
from .constants import NSFW_LEVELS
|
||||||
|
|
||||||
PreviewMedia = Mapping[str, object]
|
PreviewMedia = Mapping[str, object]
|
||||||
|
VALID_MATURE_BLUR_LEVELS = ("PG13", "R", "X", "XXX")
|
||||||
|
|
||||||
|
|
||||||
def _extract_nsfw_level(entry: Mapping[str, object]) -> int:
|
def _extract_nsfw_level(entry: Mapping[str, object]) -> int:
|
||||||
@@ -19,17 +20,36 @@ def _extract_nsfw_level(entry: Mapping[str, object]) -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_mature_threshold(settings: Mapping[str, Any] | None) -> int:
|
||||||
|
"""Resolve the configured mature blur threshold from settings.
|
||||||
|
|
||||||
|
Allowed values are ``PG13``, ``R``, ``X``, and ``XXX``. Any invalid or
|
||||||
|
missing value falls back to ``R``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(settings, Mapping):
|
||||||
|
return NSFW_LEVELS.get("R", 4)
|
||||||
|
|
||||||
|
raw_level = settings.get("mature_blur_level", "R")
|
||||||
|
normalized = str(raw_level).strip().upper()
|
||||||
|
if normalized not in VALID_MATURE_BLUR_LEVELS:
|
||||||
|
normalized = "R"
|
||||||
|
return NSFW_LEVELS.get(normalized, NSFW_LEVELS.get("R", 4))
|
||||||
|
|
||||||
|
|
||||||
def select_preview_media(
|
def select_preview_media(
|
||||||
images: Sequence[Mapping[str, object]] | None,
|
images: Sequence[Mapping[str, object]] | None,
|
||||||
*,
|
*,
|
||||||
blur_mature_content: bool,
|
blur_mature_content: bool,
|
||||||
|
mature_threshold: int | None = None,
|
||||||
) -> Tuple[Optional[PreviewMedia], int]:
|
) -> Tuple[Optional[PreviewMedia], int]:
|
||||||
"""Select the most appropriate preview media entry.
|
"""Select the most appropriate preview media entry.
|
||||||
|
|
||||||
When ``blur_mature_content`` is enabled we first try to return the first media
|
When ``blur_mature_content`` is enabled we first try to return the first media
|
||||||
item with an ``nsfwLevel`` lower than :pydata:`NSFW_LEVELS["R"]`. If none are
|
item with an ``nsfwLevel`` lower than the configured mature threshold
|
||||||
available we return the media entry with the lowest NSFW level. When the
|
(defaults to :pydata:`NSFW_LEVELS["R"]`). If none are available we return
|
||||||
setting is disabled we simply return the first entry.
|
the media entry with the lowest NSFW level. When the setting is disabled we
|
||||||
|
simply return the first entry.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not images:
|
if not images:
|
||||||
@@ -45,7 +65,9 @@ def select_preview_media(
|
|||||||
if not blur_mature_content:
|
if not blur_mature_content:
|
||||||
return selected, selected_level
|
return selected, selected_level
|
||||||
|
|
||||||
safe_threshold = NSFW_LEVELS.get("R", 4)
|
safe_threshold = (
|
||||||
|
mature_threshold if isinstance(mature_threshold, int) else NSFW_LEVELS.get("R", 4)
|
||||||
|
)
|
||||||
for candidate in candidates:
|
for candidate in candidates:
|
||||||
level = _extract_nsfw_level(candidate)
|
level = _extract_nsfw_level(candidate)
|
||||||
if level < safe_threshold:
|
if level < safe_threshold:
|
||||||
@@ -60,4 +82,4 @@ def select_preview_media(
|
|||||||
return selected, selected_level
|
return selected, selected_level
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["select_preview_media"]
|
__all__ = ["resolve_mature_threshold", "select_preview_media", "VALID_MATURE_BLUR_LEVELS"]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { modalManager } from '../managers/ModalManager.js';
|
|||||||
import { getCurrentPageState } from '../state/index.js';
|
import { getCurrentPageState } from '../state/index.js';
|
||||||
import { state } from '../state/index.js';
|
import { state } from '../state/index.js';
|
||||||
import { bulkManager } from '../managers/BulkManager.js';
|
import { bulkManager } from '../managers/BulkManager.js';
|
||||||
import { NSFW_LEVELS, getBaseModelAbbreviation } from '../utils/constants.js';
|
import { NSFW_LEVELS, getBaseModelAbbreviation, getMatureBlurThreshold } from '../utils/constants.js';
|
||||||
|
|
||||||
class RecipeCard {
|
class RecipeCard {
|
||||||
constructor(recipe, clickHandler) {
|
constructor(recipe, clickHandler) {
|
||||||
@@ -74,7 +74,8 @@ class RecipeCard {
|
|||||||
|
|
||||||
// NSFW blur logic - similar to LoraCard
|
// NSFW blur logic - similar to LoraCard
|
||||||
const nsfwLevel = this.recipe.preview_nsfw_level !== undefined ? this.recipe.preview_nsfw_level : 0;
|
const nsfwLevel = this.recipe.preview_nsfw_level !== undefined ? this.recipe.preview_nsfw_level : 0;
|
||||||
const shouldBlur = state.settings.blur_mature_content && nsfwLevel > NSFW_LEVELS.PG13;
|
const matureBlurThreshold = getMatureBlurThreshold(state.settings);
|
||||||
|
const shouldBlur = state.settings.blur_mature_content && nsfwLevel >= matureBlurThreshold;
|
||||||
|
|
||||||
if (shouldBlur) {
|
if (shouldBlur) {
|
||||||
card.classList.add('nsfw-content');
|
card.classList.add('nsfw-content');
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { showModelModal } from './ModelModal.js';
|
|||||||
import { toggleShowcase } from './showcase/ShowcaseView.js';
|
import { toggleShowcase } from './showcase/ShowcaseView.js';
|
||||||
import { bulkManager } from '../../managers/BulkManager.js';
|
import { bulkManager } from '../../managers/BulkManager.js';
|
||||||
import { modalManager } from '../../managers/ModalManager.js';
|
import { modalManager } from '../../managers/ModalManager.js';
|
||||||
import { NSFW_LEVELS, getBaseModelAbbreviation, getSubTypeAbbreviation, MODEL_SUBTYPE_DISPLAY_NAMES } from '../../utils/constants.js';
|
import { NSFW_LEVELS, getBaseModelAbbreviation, getSubTypeAbbreviation, getMatureBlurThreshold, MODEL_SUBTYPE_DISPLAY_NAMES } from '../../utils/constants.js';
|
||||||
import { MODEL_TYPES } from '../../api/apiConfig.js';
|
import { MODEL_TYPES } from '../../api/apiConfig.js';
|
||||||
import { getModelApiClient } from '../../api/modelApiFactory.js';
|
import { getModelApiClient } from '../../api/modelApiFactory.js';
|
||||||
import { showDeleteModal } from '../../utils/modalUtils.js';
|
import { showDeleteModal } from '../../utils/modalUtils.js';
|
||||||
@@ -478,7 +478,8 @@ export function createModelCard(model, modelType) {
|
|||||||
card.dataset.nsfwLevel = nsfwLevel;
|
card.dataset.nsfwLevel = nsfwLevel;
|
||||||
|
|
||||||
// Determine if the preview should be blurred based on NSFW level and user settings
|
// Determine if the preview should be blurred based on NSFW level and user settings
|
||||||
const shouldBlur = state.settings.blur_mature_content && nsfwLevel > NSFW_LEVELS.PG13;
|
const matureBlurThreshold = getMatureBlurThreshold(state.settings);
|
||||||
|
const shouldBlur = state.settings.blur_mature_content && nsfwLevel >= matureBlurThreshold;
|
||||||
if (shouldBlur) {
|
if (shouldBlur) {
|
||||||
card.classList.add('nsfw-content');
|
card.classList.add('nsfw-content');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import { showToast, copyToClipboard, getNSFWLevelName } from '../../../utils/uiHelpers.js';
|
import { showToast, copyToClipboard, getNSFWLevelName } from '../../../utils/uiHelpers.js';
|
||||||
import { state } from '../../../state/index.js';
|
import { state } from '../../../state/index.js';
|
||||||
import { getModelApiClient } from '../../../api/modelApiFactory.js';
|
import { getModelApiClient } from '../../../api/modelApiFactory.js';
|
||||||
import { NSFW_LEVELS } from '../../../utils/constants.js';
|
import { NSFW_LEVELS, getMatureBlurThreshold } from '../../../utils/constants.js';
|
||||||
import { getNsfwLevelSelector } from '../NsfwLevelSelector.js';
|
import { getNsfwLevelSelector } from '../NsfwLevelSelector.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -607,7 +607,8 @@ function applyNsfwLevelChange(mediaWrapper, nsfwLevel) {
|
|||||||
}
|
}
|
||||||
mediaWrapper.dataset.nsfwLevel = String(nsfwLevel);
|
mediaWrapper.dataset.nsfwLevel = String(nsfwLevel);
|
||||||
|
|
||||||
const shouldBlur = state.settings.blur_mature_content && nsfwLevel > NSFW_LEVELS.PG13;
|
const matureBlurThreshold = getMatureBlurThreshold(state.settings);
|
||||||
|
const shouldBlur = state.settings.blur_mature_content && nsfwLevel >= matureBlurThreshold;
|
||||||
let overlay = mediaWrapper.querySelector('.nsfw-overlay');
|
let overlay = mediaWrapper.querySelector('.nsfw-overlay');
|
||||||
let toggleBtn = mediaWrapper.querySelector('.toggle-blur-btn');
|
let toggleBtn = mediaWrapper.querySelector('.toggle-blur-btn');
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { showToast } from '../../../utils/uiHelpers.js';
|
|||||||
import { state } from '../../../state/index.js';
|
import { state } from '../../../state/index.js';
|
||||||
import { modalManager } from '../../../managers/ModalManager.js';
|
import { modalManager } from '../../../managers/ModalManager.js';
|
||||||
import { translate } from '../../../utils/i18nHelpers.js';
|
import { translate } from '../../../utils/i18nHelpers.js';
|
||||||
import { NSFW_LEVELS } from '../../../utils/constants.js';
|
import { NSFW_LEVELS, getMatureBlurThreshold } from '../../../utils/constants.js';
|
||||||
import {
|
import {
|
||||||
initLazyLoading,
|
initLazyLoading,
|
||||||
initNsfwBlurHandlers,
|
initNsfwBlurHandlers,
|
||||||
@@ -184,7 +184,8 @@ function renderMediaItem(img, index, exampleFiles) {
|
|||||||
|
|
||||||
// Check if media should be blurred
|
// Check if media should be blurred
|
||||||
const nsfwLevel = img.nsfwLevel !== undefined ? img.nsfwLevel : 0;
|
const nsfwLevel = img.nsfwLevel !== undefined ? img.nsfwLevel : 0;
|
||||||
const shouldBlur = state.settings.blur_mature_content && nsfwLevel > NSFW_LEVELS.PG13;
|
const matureBlurThreshold = getMatureBlurThreshold(state.settings);
|
||||||
|
const shouldBlur = state.settings.blur_mature_content && nsfwLevel >= matureBlurThreshold;
|
||||||
|
|
||||||
// Determine NSFW warning text based on level
|
// Determine NSFW warning text based on level
|
||||||
let nsfwText = "Mature Content";
|
let nsfwText = "Mature Content";
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { validatePriorityTagString, getPriorityTagSuggestionsMap, invalidatePrio
|
|||||||
import { bannerService } from './BannerService.js';
|
import { bannerService } from './BannerService.js';
|
||||||
import { sidebarManager } from '../components/SidebarManager.js';
|
import { sidebarManager } from '../components/SidebarManager.js';
|
||||||
|
|
||||||
|
const VALID_MATURE_BLUR_LEVELS = new Set(['PG13', 'R', 'X', 'XXX']);
|
||||||
|
|
||||||
export class SettingsManager {
|
export class SettingsManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
@@ -137,11 +139,25 @@ export class SettingsManager {
|
|||||||
backendSettings?.metadata_refresh_skip_paths ?? defaults.metadata_refresh_skip_paths
|
backendSettings?.metadata_refresh_skip_paths ?? defaults.metadata_refresh_skip_paths
|
||||||
);
|
);
|
||||||
|
|
||||||
|
merged.mature_blur_level = this.normalizeMatureBlurLevel(
|
||||||
|
backendSettings?.mature_blur_level ?? defaults.mature_blur_level
|
||||||
|
);
|
||||||
|
|
||||||
Object.keys(merged).forEach(key => this.backendSettingKeys.add(key));
|
Object.keys(merged).forEach(key => this.backendSettingKeys.add(key));
|
||||||
|
|
||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
normalizeMatureBlurLevel(value) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const normalized = value.trim().toUpperCase();
|
||||||
|
if (VALID_MATURE_BLUR_LEVELS.has(normalized)) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'R';
|
||||||
|
}
|
||||||
|
|
||||||
normalizePatternList(value) {
|
normalizePatternList(value) {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
const sanitized = value
|
const sanitized = value
|
||||||
@@ -682,6 +698,13 @@ export class SettingsManager {
|
|||||||
showOnlySFWCheckbox.checked = state.global.settings.show_only_sfw ?? false;
|
showOnlySFWCheckbox.checked = state.global.settings.show_only_sfw ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const matureBlurLevelSelect = document.getElementById('matureBlurLevel');
|
||||||
|
if (matureBlurLevelSelect) {
|
||||||
|
matureBlurLevelSelect.value = this.normalizeMatureBlurLevel(
|
||||||
|
state.global.settings.mature_blur_level
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const usePortableCheckbox = document.getElementById('usePortableSettings');
|
const usePortableCheckbox = document.getElementById('usePortableSettings');
|
||||||
if (usePortableCheckbox) {
|
if (usePortableCheckbox) {
|
||||||
usePortableCheckbox.checked = !!state.global.settings.use_portable_settings;
|
usePortableCheckbox.checked = !!state.global.settings.use_portable_settings;
|
||||||
@@ -1811,7 +1834,9 @@ export class SettingsManager {
|
|||||||
const element = document.getElementById(elementId);
|
const element = document.getElementById(elementId);
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
|
|
||||||
const value = element.value;
|
const value = settingKey === 'mature_blur_level'
|
||||||
|
? this.normalizeMatureBlurLevel(element.value)
|
||||||
|
: element.value;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Update frontend state with mapped keys
|
// Update frontend state with mapped keys
|
||||||
@@ -1834,7 +1859,12 @@ export class SettingsManager {
|
|||||||
|
|
||||||
showToast('toast.settings.settingsUpdated', { setting: settingKey.replace(/_/g, ' ') }, 'success');
|
showToast('toast.settings.settingsUpdated', { setting: settingKey.replace(/_/g, ' ') }, 'success');
|
||||||
|
|
||||||
if (settingKey === 'model_name_display' || settingKey === 'model_card_footer_action' || settingKey === 'update_flag_strategy') {
|
if (
|
||||||
|
settingKey === 'model_name_display'
|
||||||
|
|| settingKey === 'model_card_footer_action'
|
||||||
|
|| settingKey === 'update_flag_strategy'
|
||||||
|
|| settingKey === 'mature_blur_level'
|
||||||
|
) {
|
||||||
this.reloadContent();
|
this.reloadContent();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const DEFAULT_SETTINGS_BASE = Object.freeze({
|
|||||||
optimize_example_images: true,
|
optimize_example_images: true,
|
||||||
auto_download_example_images: false,
|
auto_download_example_images: false,
|
||||||
blur_mature_content: true,
|
blur_mature_content: true,
|
||||||
|
mature_blur_level: 'R',
|
||||||
autoplay_on_hover: false,
|
autoplay_on_hover: false,
|
||||||
display_density: 'default',
|
display_density: 'default',
|
||||||
card_info_display: 'always',
|
card_info_display: 'always',
|
||||||
|
|||||||
@@ -309,6 +309,15 @@ export const NSFW_LEVELS = {
|
|||||||
BLOCKED: 32
|
BLOCKED: 32
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const VALID_MATURE_BLUR_LEVELS = ['PG13', 'R', 'X', 'XXX'];
|
||||||
|
|
||||||
|
export function getMatureBlurThreshold(settings = {}) {
|
||||||
|
const rawValue = settings?.mature_blur_level;
|
||||||
|
const normalizedValue = typeof rawValue === 'string' ? rawValue.trim().toUpperCase() : '';
|
||||||
|
const levelName = VALID_MATURE_BLUR_LEVELS.includes(normalizedValue) ? normalizedValue : 'R';
|
||||||
|
return NSFW_LEVELS[levelName] ?? NSFW_LEVELS.R;
|
||||||
|
}
|
||||||
|
|
||||||
// Node type constants
|
// Node type constants
|
||||||
export const NODE_TYPES = {
|
export const NODE_TYPES = {
|
||||||
LORA_LOADER: 1,
|
LORA_LOADER: 1,
|
||||||
|
|||||||
@@ -281,6 +281,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-info">
|
||||||
|
<label for="matureBlurLevel">
|
||||||
|
{{ t('settings.contentFiltering.matureBlurThreshold') }}
|
||||||
|
<i class="fas fa-info-circle info-icon" data-tooltip="{{ t('settings.contentFiltering.matureBlurThresholdHelp') }}"></i>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control select-control">
|
||||||
|
<select id="matureBlurLevel"
|
||||||
|
onchange="settingsManager.saveSelectSetting('matureBlurLevel', 'mature_blur_level')">
|
||||||
|
<option value="PG13">{{ t('settings.contentFiltering.matureBlurThresholdOptions.pg13') }}</option>
|
||||||
|
<option value="R">{{ t('settings.contentFiltering.matureBlurThresholdOptions.r') }}</option>
|
||||||
|
<option value="X">{{ t('settings.contentFiltering.matureBlurThresholdOptions.x') }}</option>
|
||||||
|
<option value="XXX">{{ t('settings.contentFiltering.matureBlurThresholdOptions.xxx') }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Video Settings -->
|
<!-- Video Settings -->
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ describe('state module', () => {
|
|||||||
expect(defaultSettings).toMatchObject({
|
expect(defaultSettings).toMatchObject({
|
||||||
civitai_api_key: '',
|
civitai_api_key: '',
|
||||||
language: 'en',
|
language: 'en',
|
||||||
blur_mature_content: true
|
blur_mature_content: true,
|
||||||
|
mature_blur_level: 'R'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(defaultSettings.download_path_templates).toEqual(DEFAULT_PATH_TEMPLATES);
|
expect(defaultSettings.download_path_templates).toEqual(DEFAULT_PATH_TEMPLATES);
|
||||||
|
|||||||
18
tests/frontend/utils/constants.matureBlurThreshold.test.js
Normal file
18
tests/frontend/utils/constants.matureBlurThreshold.test.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { NSFW_LEVELS, getMatureBlurThreshold } from '../../../static/js/utils/constants.js';
|
||||||
|
|
||||||
|
describe('getMatureBlurThreshold', () => {
|
||||||
|
it('returns configured PG13 threshold', () => {
|
||||||
|
expect(getMatureBlurThreshold({ mature_blur_level: 'PG13' })).toBe(NSFW_LEVELS.PG13);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normalizes lowercase values', () => {
|
||||||
|
expect(getMatureBlurThreshold({ mature_blur_level: 'x' })).toBe(NSFW_LEVELS.X);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to R when value is invalid or missing', () => {
|
||||||
|
expect(getMatureBlurThreshold({ mature_blur_level: 'invalid' })).toBe(NSFW_LEVELS.R);
|
||||||
|
expect(getMatureBlurThreshold({})).toBe(NSFW_LEVELS.R);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -265,6 +265,32 @@ def test_delete_setting(manager):
|
|||||||
assert manager.get("example") is None
|
assert manager.get("example") is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_mature_blur_level_defaults_to_r(tmp_path, monkeypatch):
|
||||||
|
manager = _create_manager_with_settings(
|
||||||
|
tmp_path,
|
||||||
|
monkeypatch,
|
||||||
|
{
|
||||||
|
"blur_mature_content": True,
|
||||||
|
"folder_paths": {},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert manager.get("mature_blur_level") == "R"
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_mature_blur_level_is_normalized_to_r(tmp_path, monkeypatch):
|
||||||
|
manager = _create_manager_with_settings(
|
||||||
|
tmp_path,
|
||||||
|
monkeypatch,
|
||||||
|
{
|
||||||
|
"mature_blur_level": "unsafe",
|
||||||
|
"folder_paths": {},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert manager.get("mature_blur_level") == "R"
|
||||||
|
|
||||||
|
|
||||||
def test_model_name_display_setting_notifies_scanners(tmp_path, monkeypatch):
|
def test_model_name_display_setting_notifies_scanners(tmp_path, monkeypatch):
|
||||||
initial = {
|
initial = {
|
||||||
"libraries": {"default": {"folder_paths": {}, "default_lora_root": "", "default_checkpoint_root": "", "default_embedding_root": ""}},
|
"libraries": {"default": {"folder_paths": {}, "default_lora_root": "", "default_checkpoint_root": "", "default_embedding_root": ""}},
|
||||||
|
|||||||
@@ -1,30 +1,7 @@
|
|||||||
from py.utils.preview_selection import select_preview_media
|
import pytest
|
||||||
|
|
||||||
|
from py.utils.constants import NSFW_LEVELS
|
||||||
def test_select_preview_prefers_safe_media_when_blurred():
|
from py.utils.preview_selection import resolve_mature_threshold, select_preview_media
|
||||||
images = [
|
|
||||||
{"url": "nsfw", "type": "image", "nsfwLevel": 8},
|
|
||||||
{"url": "mid", "type": "image", "nsfwLevel": 4},
|
|
||||||
{"url": "safe", "type": "image", "nsfwLevel": 1},
|
|
||||||
]
|
|
||||||
|
|
||||||
selected, level = select_preview_media(images, blur_mature_content=True)
|
|
||||||
|
|
||||||
assert selected["url"] == "safe"
|
|
||||||
assert level == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_select_preview_returns_lowest_when_no_safe_media():
|
|
||||||
images = [
|
|
||||||
{"url": "x", "type": "image", "nsfwLevel": 16},
|
|
||||||
{"url": "r", "type": "image", "nsfwLevel": 4},
|
|
||||||
{"url": "xx", "type": "image", "nsfwLevel": 8},
|
|
||||||
]
|
|
||||||
|
|
||||||
selected, level = select_preview_media(images, blur_mature_content=True)
|
|
||||||
|
|
||||||
assert selected["url"] == "r"
|
|
||||||
assert level == 4
|
|
||||||
|
|
||||||
|
|
||||||
def test_select_preview_returns_first_when_blur_disabled():
|
def test_select_preview_returns_first_when_blur_disabled():
|
||||||
@@ -37,3 +14,36 @@ def test_select_preview_returns_first_when_blur_disabled():
|
|||||||
|
|
||||||
assert selected["url"] == "nsfw"
|
assert selected["url"] == "nsfw"
|
||||||
assert level == 32
|
assert level == 32
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("threshold_name", "expected_url"),
|
||||||
|
[
|
||||||
|
("PG13", "pg"),
|
||||||
|
("R", "pg13"),
|
||||||
|
("X", "r"),
|
||||||
|
("XXX", "x"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_select_preview_respects_configurable_threshold(threshold_name, expected_url):
|
||||||
|
images = [
|
||||||
|
{"url": "xxx", "type": "image", "nsfwLevel": NSFW_LEVELS["XXX"]},
|
||||||
|
{"url": "x", "type": "image", "nsfwLevel": NSFW_LEVELS["X"]},
|
||||||
|
{"url": "r", "type": "image", "nsfwLevel": NSFW_LEVELS["R"]},
|
||||||
|
{"url": "pg13", "type": "image", "nsfwLevel": NSFW_LEVELS["PG13"]},
|
||||||
|
{"url": "pg", "type": "image", "nsfwLevel": NSFW_LEVELS["PG"]},
|
||||||
|
]
|
||||||
|
|
||||||
|
selected, level = select_preview_media(
|
||||||
|
images,
|
||||||
|
blur_mature_content=True,
|
||||||
|
mature_threshold=NSFW_LEVELS[threshold_name],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert selected["url"] == expected_url
|
||||||
|
assert level == next(item["nsfwLevel"] for item in images if item["url"] == expected_url)
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_mature_threshold_falls_back_to_r_for_invalid_value():
|
||||||
|
assert resolve_mature_threshold({"mature_blur_level": "invalid"}) == NSFW_LEVELS["R"]
|
||||||
|
assert resolve_mature_threshold({}) == NSFW_LEVELS["R"]
|
||||||
|
|||||||
Reference in New Issue
Block a user