diff --git a/__init__.py b/__init__.py index 9e7e571..77f3cb5 100644 --- a/__init__.py +++ b/__init__.py @@ -182,6 +182,7 @@ def ui_rules(): Rule("model-preview-fallback-search-safetensors-thumbnail", False, bool), Rule("model-show-add-button", True, bool), Rule("model-show-copy-button", True, bool), + Rule("model-preview-thumbnail-type", "JPEG/WEBP", str), Rule("model-add-embedding-extension", False, bool), Rule("model-add-drag-strict-on-field", False, bool), Rule("model-add-offset", 25, int), @@ -290,6 +291,16 @@ def image_format_is_equal(f1, f2): return f1 == f2 or (f1 == "JPG" and f2 == "JPEG") or (f1 == "JPEG" and f2 == "JPG") +def is_auto_thumbnail_format(format): + return format in ["JPEG/WEBP", "WEBP/JPEG", "JPG/WEBP", "WEBP/JPG"] + + +def get_auto_thumbnail_format(original_format): + if original_format in ["JPEG", "WEBP", "JPG"]: + return original_format + return "JPEG" # default fallback + + @server.PromptServer.instance.routes.get("/model-manager/preview/get") async def get_model_preview(request): uri = request.query.get("uri") @@ -339,10 +350,13 @@ async def get_model_preview(request): image_format = image.format if response_image_format is None: response_image_format = image_format - elif not image_format_is_equal(response_image_format, image_format): + elif is_auto_thumbnail_format(response_image_format): + response_image_format = get_auto_thumbnail_format(image_format) + + if not image_format_is_equal(response_image_format, image_format): exif = image.getexif() metadata = get_image_info(image) - if response_image_format == 'JPEG' or response_image_format == 'JPG': + if response_image_format in ['JPEG', 'JPG']: image = image.convert('RGB') image_bytes = io.BytesIO() image.save(image_bytes, format=response_image_format, exif=exif, pnginfo=metadata, quality=quality) @@ -358,6 +372,9 @@ async def get_model_preview(request): image_format = image.format if response_image_format is None: response_image_format = image_format + elif is_auto_thumbnail_format(response_image_format): + response_image_format = get_auto_thumbnail_format(image_format) + w0, h0 = image.size if w is None: w = (h * w0) // h0 @@ -387,13 +404,14 @@ async def get_model_preview(request): resampling_method = Image.Resampling.BICUBIC image.thumbnail((w, h), resample=resampling_method) - if not image_format_is_equal(image_format, response_image_format) and (response_image_format == 'JPEG' or response_image_format == 'JPG'): + if not image_format_is_equal(image_format, response_image_format) and response_image_format in ['JPEG', 'JPG']: image = image.convert('RGB') image_bytes = io.BytesIO() image.save(image_bytes, format=response_image_format, exif=exif, pnginfo=metadata, quality=quality) image_data = image_bytes.getvalue() response_file_name = os.path.splitext(file_name)[0] + '.' + response_image_format.lower() + print(f"response_file_name: {response_file_name}") return web.Response( headers={ "Content-Disposition": f"inline; filename={response_file_name}", diff --git a/web/model-manager.js b/web/model-manager.js index a85e279..4882206 100644 --- a/web/model-manager.js +++ b/web/model-manager.js @@ -138,7 +138,6 @@ function imageUri(imageSearchPath = undefined, dateImageModified = undefined, wi const PREVIEW_NONE_URI = imageUri(); const PREVIEW_THUMBNAIL_WIDTH = 320; const PREVIEW_THUMBNAIL_HEIGHT = 480; -const PREVIEW_THUMBNAIL_FORMAT = "JPEG"; /** * @param {(...args) => void} callback @@ -230,6 +229,28 @@ function $checkbox(x = { $: (el) => {}, textContent: "", checked: false }) { return label; } +/** + * @returns {HTMLLabelElement} + */ +function $select(x = { $: (el) => {}, textContent: "", options: [""] }) { + const text = x.textContent; + const select = $el("select", { + name: text ?? "select", + }, x.options.map((option) => { + return $el("option", { + value: option, + }, option); + })); + const label = $el("label", [ + select, + text === "" || text === undefined || text === null ? "" : " " + text, + ]); + if (x.$ !== undefined){ + x.$(select); + } + return label; +} + /** * @param {Any} attr * @returns {HTMLDivElement} @@ -1654,6 +1675,7 @@ class ModelGrid { const addOffset = parseInt(settingsElements["model-add-offset"].value); const showModelExtension = settingsElements["model-show-label-extensions"].checked; const removeEmbeddingExtension = !settingsElements["model-add-embedding-extension"].checked; + const previewThumbnailFormat = settingsElements["model-preview-thumbnail-type"].value; if (models.length > 0) { return models.map((item) => { const previewInfo = item.preview; @@ -1706,7 +1728,7 @@ class ModelGrid { previewInfo?.dateModified, PREVIEW_THUMBNAIL_WIDTH, PREVIEW_THUMBNAIL_HEIGHT, - PREVIEW_THUMBNAIL_FORMAT, + previewThumbnailFormat, ), draggable: false, }), @@ -3221,7 +3243,8 @@ class SettingsView { case "range": setting.value = parseFloat(value); break; case "textarea": setting.value = value; break; case "number": setting.value = parseInt(value); break; - default: console.warn("Unknown settings input type!"); + case "select-one": setting.value = value; break; + default: console.warn(`Unknown settings input type '${type}'!`); } } @@ -3253,6 +3276,7 @@ class SettingsView { case "range": value = el.value; break; case "textarea": value = el.value; break; case "number": value = el.value; break; + case "select-one": value = el.value; break; default: console.warn("Unknown settings input type!"); } settingsData[setting] = value; @@ -3365,6 +3389,11 @@ class SettingsView { $: (el) => (settings["model-show-copy-button"] = el), textContent: "Show copy button", }), + $select({ + $: (el) => (settings["model-preview-thumbnail-type"] = el), + textContent: "Preview thumbnail type", + options: ["JPEG/WEBP", "JPEG", "WEBP"], + }), $el("h2", ["Model Add"]), $checkbox({ $: (el) => (settings["model-add-embedding-extension"] = el),