- Introduced fallback readers for metadata extraction when ExifTool/ffprobe are unavailable, utilizing Pillow and hachoir for images and media files respectively. - Added settings for metadata fallback preferences, allowing users to toggle image and media fallback options. - Implemented route collision logging to prevent overlapping API paths. - Added middleware for authentication on sensitive routes, ensuring secure access. - Updated API documentation to reflect new endpoints and settings. - Enhanced unit tests for metadata handling, case sensitivity in file paths, and fallback mechanisms. - Removed compatibility shims for shared modules, consolidating imports under mjr_am_shared.
148 lines
4.6 KiB
Python
148 lines
4.6 KiB
Python
"""
|
|
ComfyUI-Majoor-AssetsManager
|
|
Advanced asset browser for ComfyUI with ratings, tags, and metadata management.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
import importlib
|
|
from pathlib import Path
|
|
|
|
# ComfyUI extension metadata
|
|
NODE_CLASS_MAPPINGS = {}
|
|
NODE_DISPLAY_NAME_MAPPINGS = {}
|
|
# Resolved below after `root` is known; keep None as sentinel so imports
|
|
# executed before root assignment don't accidentally use a relative path.
|
|
WEB_DIRECTORY = None
|
|
import logging
|
|
_logger = logging.getLogger("majoor_assets_manager")
|
|
_REGISTRY_HOOKS_DONE = False
|
|
|
|
|
|
def _read_version_from_pyproject() -> str:
|
|
try:
|
|
root = Path(__file__).resolve().parent
|
|
pyproject_path = root / "pyproject.toml"
|
|
if not pyproject_path.exists():
|
|
return "0.0.0"
|
|
raw = pyproject_path.read_text(encoding="utf-8")
|
|
match = re.search(r'^version\\s*=\\s*"(.*?)"', raw, flags=re.MULTILINE)
|
|
if match:
|
|
return match.group(1).strip()
|
|
except Exception:
|
|
pass
|
|
return "0.0.0"
|
|
|
|
|
|
def _detect_branch_from_env() -> str:
|
|
candidates = (
|
|
"MAJOR_ASSETS_MANAGER_BRANCH",
|
|
"MAJOOR_ASSETS_MANAGER_BRANCH",
|
|
)
|
|
for key in candidates:
|
|
value = os.environ.get(key)
|
|
if value:
|
|
return value.strip()
|
|
return "main"
|
|
|
|
|
|
__version__ = _read_version_from_pyproject()
|
|
__branch__ = _detect_branch_from_env()
|
|
|
|
root = Path(__file__).resolve().parent
|
|
if WEB_DIRECTORY is None:
|
|
WEB_DIRECTORY = str(root / "js")
|
|
|
|
# Ensure extension-local packages (e.g. `mjr_am_backend`) are importable even when
|
|
# ComfyUI loads this module by file path without adding custom node root to sys.path.
|
|
try:
|
|
root_str = str(root)
|
|
if root_str not in sys.path:
|
|
sys.path.insert(0, root_str)
|
|
except Exception:
|
|
_logger.debug("failed to ensure extension root on sys.path", exc_info=True)
|
|
|
|
|
|
def init_prompt_server() -> None:
|
|
"""
|
|
Initialize routes against ComfyUI PromptServer.
|
|
|
|
This is kept as a convenience wrapper for ComfyUI runtime.
|
|
"""
|
|
global _REGISTRY_HOOKS_DONE
|
|
try:
|
|
_registry = importlib.import_module("mjr_am_backend.routes.registry")
|
|
|
|
register_all_routes = getattr(_registry, "register_all_routes", None)
|
|
register_routes = getattr(_registry, "register_routes", None)
|
|
install_observability = getattr(_registry, "_install_observability_on_prompt_server", None)
|
|
|
|
# Always register decorator-backed routes first; this does not require app.
|
|
if not _REGISTRY_HOOKS_DONE and callable(register_all_routes):
|
|
register_all_routes()
|
|
|
|
# Keep legacy behavior expected by import-hook tests and older runtime wiring.
|
|
if not _REGISTRY_HOOKS_DONE and callable(install_observability):
|
|
install_observability()
|
|
_REGISTRY_HOOKS_DONE = True
|
|
|
|
from server import PromptServer # type: ignore
|
|
app = getattr(PromptServer.instance, "app", None)
|
|
if app is None:
|
|
_logger.debug("PromptServer app is not available yet; route table registered only")
|
|
return
|
|
if callable(register_routes):
|
|
register_routes(app)
|
|
except Exception:
|
|
_logger.exception("failed to initialize prompt server routes")
|
|
|
|
|
|
def init(app) -> None:
|
|
"""Public explicit initializer for host integrations and tests."""
|
|
if app is None:
|
|
raise ValueError("init(app) requires a valid aiohttp app instance")
|
|
try:
|
|
from mjr_am_backend.routes.registry import register_routes
|
|
register_routes(app)
|
|
except Exception:
|
|
_logger.exception("failed to initialize routes on provided app")
|
|
|
|
|
|
def register(app, user_manager=None) -> None:
|
|
"""
|
|
Explicit host integration entrypoint.
|
|
|
|
Args:
|
|
app: aiohttp application instance
|
|
user_manager: optional ComfyUI user manager for auth-aware route policies
|
|
"""
|
|
if app is None:
|
|
raise ValueError("register(app, user_manager=None) requires a valid aiohttp app instance")
|
|
try:
|
|
from mjr_am_backend.routes.registry import register_routes
|
|
register_routes(app, user_manager=user_manager)
|
|
except Exception:
|
|
_logger.exception("failed to register routes on provided app")
|
|
|
|
|
|
try:
|
|
# Minimal ComfyUI custom_nodes import-time behavior:
|
|
# register routes/UI via explicit init path, no sys.modules mutation.
|
|
init_prompt_server()
|
|
except Exception:
|
|
_logger.exception("failed to initialize at import time")
|
|
|
|
__all__ = [
|
|
"NODE_CLASS_MAPPINGS",
|
|
"NODE_DISPLAY_NAME_MAPPINGS",
|
|
"WEB_DIRECTORY",
|
|
"__version__",
|
|
"__branch__",
|
|
"init",
|
|
"register",
|
|
"init_prompt_server",
|
|
]
|