fix(lora): show linked LoraManager videos as videos (#355)
_enrich_images marks every image without is_video by its extension, so an .mp4 preview outside the output folders shows the Video preview label and plays in the inspector. Tests cover serving the sidecar and example-image folders only while the integration is on, and a filesystem root as the sidecar folder adding nothing.
This commit is contained in:
+11
-1
@@ -750,7 +750,12 @@ class PromptManagerAPI(
|
||||
# ── Shared utilities used by multiple mixins ──────────────────────
|
||||
|
||||
def _enrich_images(self, images):
|
||||
"""Add url and thumbnail_url to a flat list of image dicts."""
|
||||
"""Add url and thumbnail_url to a flat list of image dicts.
|
||||
|
||||
Each dict without ``is_video`` also gets ``is_video`` and
|
||||
``media_type`` from its extension (#355: a linked LoraManager video
|
||||
outside the output folders is shown as a video, not a picture).
|
||||
"""
|
||||
from urllib.parse import quote as url_quote
|
||||
|
||||
output_dirs = self._get_all_output_dirs()
|
||||
@@ -760,6 +765,11 @@ class PromptManagerAPI(
|
||||
if not image_path_str:
|
||||
continue
|
||||
|
||||
if "is_video" not in image:
|
||||
video = is_video_path(image_path_str)
|
||||
image["is_video"] = video
|
||||
image["media_type"] = "video" if video else "image"
|
||||
|
||||
img_path = Path(image_path_str)
|
||||
|
||||
# Set fallback url via image ID
|
||||
|
||||
@@ -1396,6 +1396,115 @@ class TestLoraManagerPathAllowList(ImageRouteCoverageCase):
|
||||
resolve.assert_called_once_with(configured)
|
||||
|
||||
|
||||
class TestLoraManagerMediaServing(ImageRouteCoverageCase):
|
||||
"""#355: LoraManager's sidecar and example-image folders are served while
|
||||
the integration is on, only through the folder guard."""
|
||||
|
||||
async def setUpAsync(self):
|
||||
await super().setUpAsync()
|
||||
from py.config import IntegrationConfig
|
||||
|
||||
self._lm_tmp = tempfile.TemporaryDirectory(ignore_cleanup_errors=True)
|
||||
self.addCleanup(self._lm_tmp.cleanup)
|
||||
root = Path(os.path.realpath(self._lm_tmp.name))
|
||||
custom_nodes = root / "custom_nodes"
|
||||
self.lm = custom_nodes / "ComfyUI-Lora-Manager"
|
||||
(self.lm / "py").mkdir(parents=True)
|
||||
(self.lm / "__init__.py").write_text("")
|
||||
self.settings = root / "settings"
|
||||
self.settings.mkdir()
|
||||
self.sidecars = root / "sidecars"
|
||||
self.examples = root / "examples"
|
||||
self.cache = root / "cache"
|
||||
for folder in (self.sidecars, self.examples, self.cache):
|
||||
folder.mkdir()
|
||||
self.write_settings(str(self.sidecars))
|
||||
self.enabled = patch.object(IntegrationConfig, "LORA_MANAGER_ENABLED", True)
|
||||
for item in (
|
||||
self.enabled,
|
||||
patch.object(IntegrationConfig, "LORA_MANAGER_PATH", str(self.lm)),
|
||||
patch(
|
||||
"py.lora_utils.custom_nodes_directories", return_value=[custom_nodes]
|
||||
),
|
||||
patch("py.lora_utils.default_lora_roots", return_value=[]),
|
||||
patch("py.lora_utils.get_lora_image_cache_dir", return_value=self.cache),
|
||||
patch.dict(os.environ, {"LORA_MANAGER_SETTINGS_DIR": str(self.settings)}),
|
||||
):
|
||||
item.start()
|
||||
self.addCleanup(item.stop)
|
||||
|
||||
def write_settings(self, sidecar_root):
|
||||
(self.settings / "settings.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"sidecar_storage_mode": "centralized",
|
||||
"sidecar_storage_path": sidecar_root,
|
||||
"example_images_path": str(self.examples),
|
||||
"civitai_api_key": "never-served",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
async def _status(self, image_id):
|
||||
resp = await self.client.request(
|
||||
"GET", f"/prompt_manager/images/{image_id}/file"
|
||||
)
|
||||
await resp.read() # release the served file before the folder goes
|
||||
return resp.status
|
||||
|
||||
async def test_sidecar_and_example_media_are_served_while_enabled(self):
|
||||
preview = self.sidecars / "loras-0a1b2c3d" / "neon.webp"
|
||||
preview.parent.mkdir()
|
||||
preview.write_bytes(b"RIFF")
|
||||
example = self.examples / "ab12" / "1.mp4"
|
||||
example.parent.mkdir()
|
||||
example.write_bytes(b"video")
|
||||
ids = [self._link_image(preview, "a"), self._link_image(example, "b")]
|
||||
|
||||
for image_id in ids:
|
||||
self.assertEqual(await self._status(image_id), 200)
|
||||
self.enabled.stop()
|
||||
for image_id in ids:
|
||||
self.assertEqual(await self._status(image_id), 403)
|
||||
self.enabled.start()
|
||||
|
||||
async def test_a_filesystem_root_as_sidecar_folder_adds_nothing(self):
|
||||
self.write_settings(os.path.abspath(os.sep))
|
||||
dirs = self.api._lora_image_dirs()
|
||||
# An unusable layout: the default LoRA roots (none here) and the cache.
|
||||
self.assertEqual(dirs, [self.cache])
|
||||
|
||||
async def test_the_layout_folders_are_the_served_ones(self):
|
||||
dirs = self.api._lora_image_dirs()
|
||||
self.assertEqual(
|
||||
dirs,
|
||||
[
|
||||
Path(os.path.normcase(str(self.sidecars))),
|
||||
Path(os.path.normcase(str(self.examples))),
|
||||
self.cache,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class TestEnrichMarksVideos(ImageAPITestCase):
|
||||
"""#355: a linked video outside the output folders is marked as video."""
|
||||
|
||||
async def test_enrich_sets_is_video_and_media_type(self):
|
||||
elsewhere = Path(tempfile.gettempdir()) / "lora-media"
|
||||
images = [
|
||||
{"id": 1, "image_path": str(elsewhere / "a.MP4")},
|
||||
{"id": 2, "image_path": str(elsewhere / "b.png")},
|
||||
{"id": 3, "image_path": str(elsewhere / "c.mp4"), "is_video": False},
|
||||
{"id": 4, "image_path": ""},
|
||||
]
|
||||
self.api._enrich_images(images)
|
||||
self.assertEqual(
|
||||
[(i.get("is_video"), i.get("media_type")) for i in images],
|
||||
[(True, "video"), (False, "image"), (False, None), (None, None)],
|
||||
)
|
||||
self.assertEqual(images[0]["url"], "/prompt_manager/images/1/file")
|
||||
|
||||
|
||||
class TestLinkImageRelativePaths(ImageAPITestCase):
|
||||
"""POST /images/link accepts the path forms the API hands out."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user