diff --git a/README.md b/README.md index efce13a..eb4c1a2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ComfyUI Wan Resolutions +# ComfyUI WanResolutions ComfyUI custom node that outputs `width` and `height` presets for Wan 2.2 video generation. @@ -7,6 +7,7 @@ ComfyUI custom node that outputs `width` and `height` presets for Wan 2.2 video - Lets you choose an `aspect_ratio` and resolution quality tier - Outputs `INT` values: `width`, `height` - Optional `IMAGE` input auto-matches the closest supported aspect ratio as an override +- Optional `round_to_16` snaps output to dimensions divisible by 16 while staying close to the target ratio - Keeps equivalent quality tier when you switch aspect ratios in the UI Supported aspect ratios: @@ -34,10 +35,11 @@ Restart ComfyUI after installing. ## Usage -1. Add the `Wan Resolution` node. +1. Add the `WanResolutions` node. 2. Pick `aspect_ratio` and `resolution`. -3. Optional: connect an `IMAGE` input to auto-select the nearest aspect ratio. -4. Connect `width` and `height` outputs to your downstream nodes. +3. Optional: enable `round_to_16` for dimensions divisible by 16. +4. Optional: connect an `IMAGE` input to auto-select the nearest aspect ratio. +5. Connect `width` and `height` outputs to your downstream nodes. ## License diff --git a/__init__.py b/__init__.py index 4c21349..203a01f 100644 --- a/__init__.py +++ b/__init__.py @@ -1,11 +1,11 @@ -from .wan_resolution import WanResolution +from .wanresolutions import WanResolutions NODE_CLASS_MAPPINGS = { - "WanResolution": WanResolution, + "WanResolutions": WanResolutions, } NODE_DISPLAY_NAME_MAPPINGS = { - "WanResolution": "Wan Resolution", + "WanResolutions": "WanResolutions", } # Any .js files in this directory will be loaded by the frontend diff --git a/js/wan_resolution.js b/js/wanresolutions.js similarity index 97% rename from js/wan_resolution.js rename to js/wanresolutions.js index c999508..536ea1e 100644 --- a/js/wan_resolution.js +++ b/js/wanresolutions.js @@ -115,9 +115,9 @@ function tierIndexForValue(aspectRatio, value) { } app.registerExtension({ - name: "wan_resolution.dynamic_resolution_list", + name: "wanresolutions.dynamic_resolution_list", async nodeCreated(node) { - if (node.comfyClass !== "WanResolution") return; + if (node.comfyClass !== "WanResolutions") return; const aspectWidget = node.widgets?.find((w) => w.name === "aspect_ratio"); const resWidget = node.widgets?.find((w) => w.name === "resolution"); diff --git a/pyproject.toml b/pyproject.toml index 4255c2b..88c721a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "comfyui-wan-resolutions" -description = "ComfyUI custom node for selecting WAN resolution presets by aspect ratio." +name = "comfyui-wanresolutions" +description = "ComfyUI custom node for selecting Wan resolution presets by aspect ratio." authors = [ { name = "boobkake22", email = "boobkake22@gmail.com" } ] @@ -16,5 +16,5 @@ Repository = "https://github.com/boobkake22/ComfyUI-WanResolutions" [tool.comfy] PublisherId = "boobkake22" -DisplayName = "Wan Resolutions" +DisplayName = "WanResolutions" Icon = "" diff --git a/wan_resolution.py b/wanresolutions.py similarity index 76% rename from wan_resolution.py rename to wanresolutions.py index 093f3c4..3faf954 100644 --- a/wan_resolution.py +++ b/wanresolutions.py @@ -2,11 +2,12 @@ import re from typing import Dict, List, Optional, Tuple -class WanResolution: +class WanResolutions: """ - WAN Resolution + WanResolutions - Choose aspect ratio and quality tier. - Optional IMAGE input can auto-select the closest aspect ratio. + - Optional round_to_16 can snap output dimensions to multiples of 16. - Outputs width,height as INT. """ @@ -124,6 +125,7 @@ class WanResolution: "required": { "aspect_ratio": (aspect_choices, {"default": default_ar}), "resolution": (all_resolution_choices, {"default": default_res}), + "round_to_16": ("BOOLEAN", {"default": False}), }, "optional": { "image": ("IMAGE",), @@ -166,6 +168,63 @@ class WanResolution: w, h, _ = rows[0] return w, h + @staticmethod + def _round_to_multiple(value: float, multiple: int) -> int: + if multiple <= 0: + return int(round(value)) + return max(multiple, int(round(float(value) / float(multiple))) * multiple) + + @classmethod + def _round_resolution_preserve_aspect( + cls, + width: int, + height: int, + multiple: int = 16, + ) -> Tuple[int, int]: + """ + Snap dimensions to a required multiple while keeping aspect ratio as close + as possible to the requested width/height. + """ + if width <= 0 or height <= 0: + return ( + cls._round_to_multiple(max(1, width), multiple), + cls._round_to_multiple(max(1, height), multiple), + ) + + target_ratio = float(width) / float(height) + target_area = width * height + + base_w = cls._round_to_multiple(width, multiple) + base_h = cls._round_to_multiple(height, multiple) + + width_candidates = {max(multiple, base_w + (step * multiple)) for step in range(-3, 4)} + height_candidates = {max(multiple, base_h + (step * multiple)) for step in range(-3, 4)} + + candidates = set() + + # Generate options by preserving ratio from width and from height. + for w in width_candidates: + h = cls._round_to_multiple(float(w) / target_ratio, multiple) + candidates.add((w, h)) + for h in height_candidates: + w = cls._round_to_multiple(float(h) * target_ratio, multiple) + candidates.add((w, h)) + + # Add a small local grid around the nearest rounded size. + for w in width_candidates: + for h in height_candidates: + candidates.add((w, h)) + + best_w, best_h = min( + candidates, + key=lambda wh: ( + abs((float(wh[0]) / float(wh[1])) - target_ratio), + abs(wh[0] - width) + abs(wh[1] - height), + abs((wh[0] * wh[1]) - target_area), + ), + ) + return int(best_w), int(best_h) + @staticmethod def _parse_aspect_ratio_value(aspect_ratio: str) -> Optional[float]: m = re.match(r"^\s*(\d+)\s*:\s*(\d+)\s*$", aspect_ratio or "") @@ -234,6 +293,7 @@ class WanResolution: aspect_ratio: str, resolution: str, image=None, + round_to_16: bool = False, ): resolved_aspect = aspect_ratio image_dims = self._image_dimensions(image) @@ -242,4 +302,6 @@ class WanResolution: resolved_aspect = self._best_aspect_ratio(image_w, image_h) w, h = self._parse_resolution(resolved_aspect, resolution) + if round_to_16: + w, h = self._round_resolution_preserve_aspect(w, h, multiple=16) return (int(w), int(h))