diff --git a/README-ja.md b/README-ja.md index c4119c2..29c13a6 100644 --- a/README-ja.md +++ b/README-ja.md @@ -30,7 +30,19 @@ デフォルトのローダーとほぼ同じです。LoRAファイルの指定部分がコンボボックスではなくテキストになっているのでSingle Line Pickerを直接繋げることができます。 -出力パラメータ`stem`を持っています。LoRAファイルの拡張子を除いたファイル名を出力します([audioscavenger/save-image-extended-comfyui](https://github.com/audioscavenger/save-image-extended-comfyui) はLoRAの名前を参照することができますが、SLP Lora Loaderの`lora_name`を参照しようとするとエラーが出たため名前を`lora_name_text`に変更しています。この`lora_name_text`を参照したとしてもエラーにはなりませんが、正常な値は取得できません。`stem`出力を加工するなりして対応してください)。 +出力パラメータ`stem`を持っています。LoRAファイルの拡張子を除いたファイル名を出力します。次節のSLP Filename Prefixで利用できます。 + + +### SLP Filename Prefix + +「画像を保存」などのファイルを書き出すノードは`filename_prefix(ファイル名_プレフィックス)`パラメータを持ち`%date:%`方式で現在の日付時刻を設定できます。ただしこれはフロントエンドで前処理されるため、実行中の出力と組み合わせることが難しいのです。`filename_prefix`にインプット接続しても日付時刻の処理はされません。 + +それを解決するためにこのノードでは、日付時刻の処理に加えてプレースホルダによる文字列置き換えを行った結果を出力します。「画像を保存」などの`filename_prefix`に有効な値を渡すことができます。 + +プレースホルダは`$1` `$2` `$3` `$4`と最大4個作ることができ、それぞれ同名のインプットで置き換えられます。入力は文字列と数値に対応しています。 + +![screecshot03](./assets/slp03.jpg) + ## Changelog diff --git a/README.md b/README.md index 9a87eac..fb3b3f5 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,19 @@ When select this node, a button marked with the ![icon](./assets/lora.svg) icon It's almost identical to the default loader. Since the LoRA file selection part is text instead of a combo box so that connect a Single Line Picker directly. -It has an output parameter called `stem`. It outputs LoRA filename without file extension ([audioscavenger/save-image-extended-comfyui](https://github.com/audioscavenger/save-image-extended-comfyui) can reference LoRA name, but since attempting to reference `lora_name` in the SLP Lora Loader caused an error, the name has been changed to `lora_name_text`. Referencing this `lora_name_text` will not cause an error, but it will not retrieve the correct value. Please handle this by modifying the `stem` output or similar means). +It has an output parameter called `stem`. It outputs LoRA filename without file extension. Available in the next section's SLP Filename Prefix. + + +### SLP Filename Prefix + +Nodes that export files, such as "Save Image" feature a `filename_prefix` parameter that can be set to the current date and time using the `%date:%` format. However, since this is preprocessed by the frontend, it is difficult to combine with backend's runtime output. Connecting an input to `filename_prefix` will not process the date and time. + +To resolve this, this node outputs the result of processing the date and time, along with replacing placeholders with strings. You can pass valid value for `filename_prefix` such as "Save Image". + +Placeholders can be created up to four: `$1`, `$2`, `$3`, and `$4`. Each is replaced with an input of the same name. Inputs support string and numeric values. + +![screecshot03](./assets/slp03.jpg) + ## Changelog diff --git a/__init__.py b/__init__.py index 4d5f563..a52a66b 100644 --- a/__init__.py +++ b/__init__.py @@ -1,17 +1,19 @@ from .single_line_picker import SLPSingleLinePicker from .slp_lora_loader import SLPLoraLoader, SLPLoraLoaderModelOnly - +from .slp_filename_prefix import SLPFilenamePrefix NODE_CLASS_MAPPINGS = { SLPSingleLinePicker.NAME: SLPSingleLinePicker, SLPLoraLoader.NAME: SLPLoraLoader, SLPLoraLoaderModelOnly.NAME: SLPLoraLoaderModelOnly, + SLPFilenamePrefix.NAME: SLPFilenamePrefix, } NODE_DISPLAY_NAME_MAPPINGS = { SLPSingleLinePicker.NAME: SLPSingleLinePicker.DISPLAY_NAME, SLPLoraLoader.NAME: SLPLoraLoader.DISPLAY_NAME, SLPLoraLoaderModelOnly.NAME: SLPLoraLoaderModelOnly.DISPLAY_NAME, + SLPFilenamePrefix.NAME: SLPFilenamePrefix.DISPLAY_NAME, } diff --git a/assets/slp03.jpg b/assets/slp03.jpg new file mode 100644 index 0000000..b5d5a61 Binary files /dev/null and b/assets/slp03.jpg differ diff --git a/js/slp_filename_prefix.js b/js/slp_filename_prefix.js new file mode 100644 index 0000000..4a76ae8 --- /dev/null +++ b/js/slp_filename_prefix.js @@ -0,0 +1,22 @@ +const { app } = window.comfyAPI.app; +const { applyTextReplacements } = window.comfyAPI.utils; + +app.registerExtension({ + name: "hetima.SLPFilenamePrefix", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "HetimaSLPFilenamePrefix") { + + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + const ret = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; + + const widget = this.widgets.find((w) => w.name === "filename_prefix"); + widget.serializeValue = () => { + return applyTextReplacements(app, widget.value); + }; + + return ret; + }; + } + }, +}); diff --git a/slp_filename_prefix.py b/slp_filename_prefix.py new file mode 100644 index 0000000..11bf8e0 --- /dev/null +++ b/slp_filename_prefix.py @@ -0,0 +1,62 @@ +import re + +class AnyType(str): + """A special class that is always equal in not equal comparisons. Credit to pythongosssss""" + def __ne__(self, __value: object) -> bool: + return False + +any_type = AnyType("*") + +class SLPFilenamePrefix: + + NAME = "HetimaSLPFilenamePrefix" + DISPLAY_NAME = "SLP Filename Prefix" + + CATEGORY = "SingleLinePicker" + DESCRIPTION = "Builds a filename_prefix and return one includes placeholder replacements." + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "filename_prefix": ("STRING", {"multiline": False, "default": "%date:yyyy-MM-dd%/$1", "tooltip": 'can include "%date:%", "$1", "$2", "$3" and "$4"'}), + }, + "optional": { + "$1": (any_type, {"multiline": False, "default": "", "forceInput": True, "tooltip": 'replace "$1" with this value'}), + "$2": (any_type, {"multiline": False, "default": "", "forceInput": True, "tooltip": 'replace "$2" with this value'}), + "$3": (any_type, {"multiline": False, "default": "", "forceInput": True, "tooltip": 'replace "$3" with this value'}), + "$4": (any_type, {"multiline": False, "default": "", "forceInput": True, "tooltip": 'replace "$4" with this value'}), + }, + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("STRING",) + OUTPUT_TOOLTIPS = ("built filename_prefix",) + OUTPUT_NODE = True + FUNCTION = "execute" + + def to_storing(self, val): + if isinstance(val, str) and len(val) > 0: + val = val.splitlines()[0] + trans = val.maketrans("?/<>\\:*|\"", "_________") + val = val.translate(trans) + return val + if isinstance(val, (int, float, bool)): + return str(val) + return "" + + def execute(self, **kwargs): + filename_prefix = kwargs["filename_prefix"] + if not filename_prefix: + return "" + + s1 = self.to_storing(kwargs.get("$1", "")) + s2 = self.to_storing(kwargs.get("$2", "")) + s3 = self.to_storing(kwargs.get("$3", "")) + s4 = self.to_storing(kwargs.get("$4", "")) + mapping = {"$1": s1, "$2": s2, "$3": s3, "$4": s4,} + + pattern = re.compile("|".join(re.escape(k) for k in mapping.keys())) + filename_prefix = pattern.sub(lambda m: mapping[m.group(0)], filename_prefix) + + return (filename_prefix,)