diff --git a/README.md b/README.md index 2c4e2d9..811d219 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,9 @@ The **Character JSON Reader** node allows you to load character definitions from - **Automatic File Discovery**: Scans the `nodes/character_reader/characters/` folder for `.json` files. - **Selection Modes**: - `manual`: Select a specific file from the dropdown. - - `sequential`: Cycles through files based on the `index` input. - - `random`: Picks a random file using the `index` as a seed. + - `sequential`: Cycles through files, starting from the selected file. + - `random`: Picks a random file. Uses a fresh random seed on every execution. +- **Repeat Count**: Keeps the same selection for a specified number of executions before changing. - **Structured Outputs**: Provides 21 distinct output pins covering identity, wardrobe, and style. - **Prompt-Ready Formatting**: Automatically appends a comma to every non-empty output string. - **Robustness**: Ensures all outputs are valid strings, even if fields are missing from the JSON. @@ -24,11 +25,77 @@ The **Resolution Reader** node reads resolution configurations from text files. #### Features: - **Selection Modes**: - - `manual`: Uses the first line of the selected file. - - `sequential`: Cycles through lines in the file based on the `index` input. - - `random`: Picks a random line from the file using the `index` as a seed. + - `manual`: Uses the selected line from the dropdown. + - `sequential`: Cycles through lines in the file, starting from the selected line. + - `random`: Picks a random line from the file. Uses a fresh random seed on every execution. +- **Repeat Count**: Keeps the same selection for a specified number of executions before changing. - **Outputs**: Provides `width` (INT), `height` (INT), and `upscale` (FLOAT). +### Lora Selector + +The **Lora Selector** node allows you to select a folder of Lora files (from `ComfyUI/models/Lora/Illustrious/`) and pick one `.safetensors` file at a time, either randomly or sequentially. + +#### Features: +- **Folder Selection**: Scans `ComfyUI/models/Lora/Illustrious/` for subdirectories. +- **Selection Modes**: + - `Random`: Picks a random Lora from the folder. Uses a fresh random seed on every execution. + - `Sequential`: Cycles through the Lora files in the folder, starting from the `manual_index`. + - `Manual`: Selects the Lora at the specified `manual_index`. +- **Repeat Count**: Keeps the same selection for a specified number of executions before changing. +- **Manual Index**: Specifies the starting index for `Sequential` mode or the specific index for `Manual` mode. +- **Outputs**: + - `lora_name` (STRING): The relative path to the selected Lora. + - `total_loras` (INT): The total number of Lora files in the selected folder. + +### Checkpoint Selector + +The **Checkpoint Selector** node allows you to select a folder of Checkpoint files (from `ComfyUI/models/Stable-diffusion/`) and pick one `.safetensors` or `.ckpt` file at a time, either randomly or sequentially. + +#### Features: +- **Folder Selection**: Scans `ComfyUI/models/Stable-diffusion/` (and other checkpoint paths) for subdirectories. +- **Selection Modes**: + - `Random`: Picks a random Checkpoint from the folder. Uses a fresh random seed on every execution. + - `Sequential`: Cycles through the Checkpoint files in the folder, starting from the `manual_index`. + - `Manual`: Selects the Checkpoint at the specified `manual_index`. +- **Repeat Count**: Keeps the same selection for a specified number of executions before changing. +- **Manual Index**: Specifies the starting index for `Sequential` mode or the specific index for `Manual` mode. +- **Outputs**: + - `checkpoint_name` (STRING): The relative path to the selected Checkpoint. + - `total_checkpoints` (INT): The total number of Checkpoint files in the selected folder. + +### Checkpoint Loader (From String) + +The **Checkpoint Loader (From String)** node loads a checkpoint model using a string input (e.g., from the **Checkpoint Selector** node). + +#### Inputs: +- `ckpt_name` (STRING): The name/path of the checkpoint to load. + +#### Outputs: +- `MODEL`: The loaded model. +- `CLIP`: The loaded CLIP model. +- `VAE`: The loaded VAE model. + +### AODH Image Saver (Metadata) + +The **AODH Image Saver (Metadata)** node saves images with comprehensive metadata in both A1111-compatible and extended ComfyUI formats. + +#### Features: +- **Dynamic Directory Support**: Specify a `save_directory` with support for date formatting: + - `date:yyyy-MM-dd`: Replaces with formatted date (e.g., `2024-05-20`). + - Standard `strftime` patterns like `%Y-%m-%d`. +- **Comprehensive Metadata**: Embeds prompt, sampling parameters, model info, and LoRA details. +- **LoRA Handling**: Automatically strips directory paths from LoRA names for cleaner metadata. +- **Image Pass-through**: Includes an image output pin to continue the workflow after saving. + +#### Inputs: +- `images` (IMAGE): The images to save. +- `filename_prefix` (STRING): The prefix for the saved files. +- `save_directory` (STRING): The folder to save images in (defaults to current date). +- Various optional metadata fields (prompts, checkpoint, lora, etc.). + +#### Outputs: +- `images` (IMAGE): The input images passed through. + #### JSON Structure: Place your character JSON files in the `nodes/character_reader/characters/` directory. The expected format is: diff --git a/character_ids.txt b/character_ids.txt new file mode 100644 index 0000000..016c056 --- /dev/null +++ b/character_ids.txt @@ -0,0 +1,35 @@ +aerith_gainsborough +android_18 +anya_forger +bulma +camilla +cammy +chun_li +ciri +hatsune_miku +jessica_rabbit +jessie +jinx +k/da_all_out_ahri +k/da_all_out_akali +k/da_all_out_evelynn +k/da_all_out_kai'sa +komi_shouko +lara_croft_classic +lulu (ffx) +majin_android_21 +marin_kitagawa +nessa +princess_peach +princess_zelda_botw +riju +rosalina +rouge_the_bat +ryouko_(tenchi_muyou!) +samus_aran +sucy_manbavaran +tifa_lockhart +urbosa +yor_briar +y'shtola_rhul +yuna_ffx diff --git a/comfyui_metadata_spec.json b/comfyui_metadata_spec.json new file mode 100644 index 0000000..31d482c --- /dev/null +++ b/comfyui_metadata_spec.json @@ -0,0 +1,122 @@ +{ + "_description": "Sample metadata structure for ComfyUI-generated images with A1111-compatible format.\n This format allows the app to parse ComfyUI images using the same parser as A1111 images,\n while preserving ComfyUI-specific metadata for advanced features.\n \n A ComfyUI node should embed this metadata in the 'parameters' PNG text chunk.", + + "parameters": "masterpiece, best quality, amazing quality, very aesthetic, high resolution, ultra-detailed, 1girl, solo, looking at viewer, smile, long hair, blue eyes, school uniform\nNegative prompt: embedding:Illustrious/lazyneg_1760455, artist name, signature, watermark, blurry, low quality, bad anatomy, text, censored, deformed, bad hand\nSteps: 30, Sampler: euler_ancestral, CFG scale: 6.0, Seed: 927008042550191, Size: 1280x800, Model: illustrij_v20.safetensors, Model hash: a1b2c3d4e5f6, Clip skip: 2, RNG: GPU, Tiling: false, Restore faces: false, Hires upscale: 1.5, Hires steps: 15, Hires upscaler: RealESRGAN_x4plus_anime_6B, Denoising strength: 0.4", + + "comfyui": true, + + "comfyui_metadata": { + "_description": "Extended metadata specific to ComfyUI workflow", + + "workflow_name": "Character Generator", + "workflow_version": "1.0.0", + + "generation": { + "checkpoint": "Illustrious/illustrij_v20.safetensors", + "checkpoint_hash": "a1b2c3d4e5f6", + "vae": "sdxl_vae.safetensors", + "clip_skip": 2, + "lora": [ + { + "name": "Illustrious/Styles/Noob/jyojifuku_noobai_V1.0.safetensors", + "strength_model": 1.0, + "strength_clip": 1.0 + } + ] + }, + + "sampling": { + "sampler": "euler_ancestral", + "scheduler": "karras", + "steps": 30, + "cfg": 6.0, + "seed": 927008042550191, + "batch_size": 1 + }, + + "resolution": { + "width": 1280, + "height": 800, + "upscale_factor": 1.5, + "upscaler": "RealESRGAN_x4plus_anime_6B", + "hires_steps": 15, + "denoise_strength": 0.4 + }, + + "prompt_structure": { + "positive": { + "full": "masterpiece, best quality, amazing quality, very aesthetic, high resolution, ultra-detailed, 1girl, solo, looking at viewer, smile, long hair, blue eyes, school uniform", + "quality_tags": "masterpiece, best quality, amazing quality, very aesthetic, high resolution, ultra-detailed", + "character": { + "name": "sucy_manbavaran", + "description": "1girl, solo, looking at viewer, smile, long hair, blue eyes", + "outfit": "school uniform" + }, + "style_tags": "" + }, + "negative": { + "full": "embedding:Illustrious/lazyneg_1760455, artist name, signature, watermark, blurry, low quality, bad anatomy, text, censored, deformed, bad hand", + "embeddings": ["Illustrious/lazyneg_1760455"] + } + }, + + "post_processing": { + "detailers": [ + { + "type": "FaceDetailer", + "enabled": true, + "guide_size": 512, + "steps": 20, + "denoise": 0.25 + }, + { + "type": "HandDetailer", + "enabled": true, + "guide_size": 512, + "steps": 24, + "denoise": 0.35 + } + ], + "color_match": { + "enabled": true, + "color_space": "LAB", + "luminance_factor": 1.0, + "color_intensity_factor": 1.05 + } + }, + + "workflow": { + "nodes": { + "prompt_node_id": "374", + "prompt_node_type": "ShowText|pysssss", + "prompt_node_title": "Full Prompt", + "seed_node_id": "164", + "sampler_node_id": "169", + "checkpoint_loader_id": "332" + }, + "groups": [ + "Sampler", + "Character", + "FaceDetailer", + "HandDetailer", + "Upscaler" + ] + } + }, + + "app_metadata": { + "_description": "Optional metadata for this app to enable advanced features", + + "tags": ["1girl", "solo", "school uniform", "smile"], + "character": "sucy_manbavaran", + "rating": "safe", + "favorite": false, + "collections": ["Illustrious Characters"], + + "custom_fields": { + "artist": "", + "source_url": "", + "notes": "" + } + } +} diff --git a/extract_character_ids.py b/extract_character_ids.py new file mode 100644 index 0000000..891ff87 --- /dev/null +++ b/extract_character_ids.py @@ -0,0 +1,44 @@ +import json +import os + +def extract_ids(): + characters_dir = 'nodes/character_reader/characters' + output_file = 'character_ids.txt' + + if not os.path.exists(characters_dir): + print(f"Directory not found: {characters_dir}") + return + + character_ids = [] + + # List all files in the directory + files = os.listdir(characters_dir) + # Sort files to ensure consistent output order + files.sort() + + for filename in files: + if filename.endswith('.json'): + filepath = os.path.join(characters_dir, filename) + try: + with open(filepath, 'r', encoding='utf-8') as f: + data = json.load(f) + if 'character_id' in data: + character_ids.append(data['character_id']) + else: + print(f"Warning: 'character_id' not found in {filename}") + except json.JSONDecodeError: + print(f"Error decoding JSON in {filename}") + except Exception as e: + print(f"Error reading {filename}: {e}") + + # Write to output file + try: + with open(output_file, 'w', encoding='utf-8') as f: + for char_id in character_ids: + f.write(f"{char_id}\n") + print(f"Successfully wrote {len(character_ids)} character IDs to {output_file}") + except Exception as e: + print(f"Error writing to output file: {e}") + +if __name__ == "__main__": + extract_ids() diff --git a/nodes/__init__.py b/nodes/__init__.py index 46967a7..9d0e955 100644 --- a/nodes/__init__.py +++ b/nodes/__init__.py @@ -2,19 +2,31 @@ from .character_reader import NODE_CLASS_MAPPINGS as CR_CLASS, NODE_DISPLAY_NAME from .resolution_reader import NODE_CLASS_MAPPINGS as RR_CLASS, NODE_DISPLAY_NAME_MAPPINGS as RR_DISPLAY from .reenforcer import NODE_CLASS_MAPPINGS as RE_CLASS, NODE_DISPLAY_NAME_MAPPINGS as RE_DISPLAY from .lora_from_string import NODE_CLASS_MAPPINGS as LFS_CLASS, NODE_DISPLAY_NAME_MAPPINGS as LFS_DISPLAY +from .lora_selector import NODE_CLASS_MAPPINGS as LS_CLASS, NODE_DISPLAY_NAME_MAPPINGS as LS_DISPLAY +from .checkpoint_selector import NODE_CLASS_MAPPINGS as CS_CLASS, NODE_DISPLAY_NAME_MAPPINGS as CS_DISPLAY +from .checkpoint_from_string import NODE_CLASS_MAPPINGS as CFS_CLASS, NODE_DISPLAY_NAME_MAPPINGS as CFS_DISPLAY +from .metadata_saver import NODE_CLASS_MAPPINGS as MS_CLASS, NODE_DISPLAY_NAME_MAPPINGS as MS_DISPLAY NODE_CLASS_MAPPINGS = { **CR_CLASS, **RR_CLASS, **RE_CLASS, - **LFS_CLASS + **LFS_CLASS, + **LS_CLASS, + **CS_CLASS, + **CFS_CLASS, + **MS_CLASS } NODE_DISPLAY_NAME_MAPPINGS = { **CR_DISPLAY, **RR_DISPLAY, **RE_DISPLAY, - **LFS_DISPLAY + **LFS_DISPLAY, + **LS_DISPLAY, + **CS_DISPLAY, + **CFS_DISPLAY, + **MS_DISPLAY } __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] diff --git a/nodes/character_reader/character_reader.py b/nodes/character_reader/character_reader.py index db67a5a..f12016b 100644 --- a/nodes/character_reader/character_reader.py +++ b/nodes/character_reader/character_reader.py @@ -30,45 +30,59 @@ class CharacterJsonReader: "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", - "STRING", "FLOAT", "STRING" + "STRING", "FLOAT", "STRING", "INT" ) RETURN_NAMES = ( "name", "base_specs", "hair", "eyes", "expression", "hands", "arms", "torso", "pelvis", "legs", "feet", "distinguishing_marks", "inner_layer", "outer_layer", "lower_body", "footwear", "gloves", "accessories", "aesthetic", "primary_color", "secondary_color", "tertiary_color", - "lora_name", "lora_weight", "lora_triggers" + "lora_name", "lora_weight", "lora_triggers", "total_characters" ) FUNCTION = "read_character" CATEGORY = "AODH Pack" - _current_index = 0 - _current_count = 0 - _last_selection = None + def __init__(self): + self.current_index = 0 + self.current_count = 0 + self.last_selection = None + self.last_start_file = None def read_character(self, character_file, selection_mode, repeat_count): + repeat_count = max(1, repeat_count) base_path = os.path.dirname(os.path.realpath(__file__)) char_dir = os.path.join(base_path, "characters") characters = sorted([f for f in os.listdir(char_dir) if f.endswith('.json')]) if not characters: - return ("",) * 22 + ("", 0.0, "") + return ("",) * 22 + ("", 0.0, "", 0) + + # Reset sequence if character_file changes + if self.last_start_file != character_file: + try: + self.current_index = characters.index(character_file) + except ValueError: + self.current_index = 0 + self.current_count = 0 + self.last_start_file = character_file if selection_mode == "manual": selected_file = character_file elif selection_mode == "sequential": - if self.__class__._current_count >= repeat_count: - self.__class__._current_index += 1 - self.__class__._current_count = 0 - selected_file = characters[self.__class__._current_index % len(characters)] - self.__class__._current_count += 1 + if self.current_count >= repeat_count: + self.current_index += 1 + self.current_count = 0 + selected_file = characters[self.current_index % len(characters)] + self.current_count += 1 elif selection_mode == "random": - if self.__class__._current_count >= repeat_count or self.__class__._last_selection is None or self.__class__._last_selection not in characters: - self.__class__._last_selection = random.choice(characters) - self.__class__._current_count = 0 - selected_file = self.__class__._last_selection - self.__class__._current_count += 1 + if self.current_count >= repeat_count or self.last_selection is None or self.last_selection not in characters: + # Use a local Random instance to ensure randomness regardless of global seed + rng = random.Random() + self.last_selection = rng.choice(characters) + self.current_count = 0 + selected_file = self.last_selection + self.current_count += 1 else: selected_file = character_file @@ -114,4 +128,5 @@ class CharacterJsonReader: get_val(lora, "lora_name", prepend_comma=False), float(lora.get("lora_weight", 0.0) if lora.get("lora_weight") else 0.0), get_val(lora, "lora_triggers"), + len(characters), ) diff --git a/nodes/character_reader/characters/android_18.json b/nodes/character_reader/characters/android_18.json index 5a9c5ca..ebeffbf 100644 --- a/nodes/character_reader/characters/android_18.json +++ b/nodes/character_reader/characters/android_18.json @@ -15,7 +15,7 @@ }, "wardrobe": { "inner_layer": "black short-sleeved shirt", - "outer_layer": "blue denim vest, Red Ribbon logo on back", + "outer_layer": "blue denim vest, 'RR' text on back", "lower_body": "blue denim skirt, black leggings", "footwear": "brown boots", "gloves": "", diff --git a/nodes/character_reader/characters/anya_forger.json b/nodes/character_reader/characters/anya_forger.json index 9e07e41..fa603b1 100644 --- a/nodes/character_reader/characters/anya_forger.json +++ b/nodes/character_reader/characters/anya_forger.json @@ -1,5 +1,5 @@ { - "character_id": "anya_forger", + "character_id": "anya_(spy_x_family)", "identity": { "base_specs": "1girl, small build, fair skin", "hair": "short pink hair, two small horns (hair ornaments)", diff --git a/nodes/character_reader/characters/biwa_hayahide.json b/nodes/character_reader/characters/biwa_hayahide.json new file mode 100644 index 0000000..8d85e92 --- /dev/null +++ b/nodes/character_reader/characters/biwa_hayahide.json @@ -0,0 +1,35 @@ +{ + "character_id": "biwa_hayahide_(Umamusume)", + "identity": { + "base_specs": "1girl, horse ears, horse tail, tall", + "hair": "long grey hair, wild hair", + "eyes": "purple eyes, red framed glasses", + "expression": "thinking", + "hands": "", + "arms": "", + "torso": "large breasts", + "pelvis": "", + "legs": "", + "feet": "", + "distinguishing_marks": "" + }, + "wardrobe": { + "inner_layer": "white shirt", + "outer_layer": "tracen school uniform", + "lower_body": "pleated skirt", + "footwear": "heeled shoes", + "gloves": "", + "accessories": "" + }, + "styles": { + "aesthetic": "intellectual, cool", + "primary_color": "maroon", + "secondary_color": "white", + "tertiary_color": "grey" + }, + "lora": { + "lora_name": "", + "lora_weight": 1.0, + "lora_triggers": "" + } +} \ No newline at end of file diff --git a/nodes/character_reader/characters/bulma.json b/nodes/character_reader/characters/bulma.json index 213d735..0223406 100644 --- a/nodes/character_reader/characters/bulma.json +++ b/nodes/character_reader/characters/bulma.json @@ -15,10 +15,10 @@ }, "wardrobe": { "inner_layer": "", - "outer_layer": "pink dress, 'Bulma' text on front", - "lower_body": "dress", - "footwear": "purple sneakers, white socks", - "gloves": "purple gloves", + "outer_layer": "black playboy bunny", + "lower_body": "pantyhose", + "footwear": "red high heels", + "gloves": "detatched cuffs", "accessories": "red hair ribbon, dragon radar" }, "styles": { diff --git a/nodes/character_reader/characters/camilla.json b/nodes/character_reader/characters/camilla.json index c5fafd8..96d9c62 100644 --- a/nodes/character_reader/characters/camilla.json +++ b/nodes/character_reader/characters/camilla.json @@ -1,5 +1,5 @@ { - "character_id": "camilla", + "character_id": "camilla_(fire_emblem)", "identity": { "base_specs": "1girl, curvaceous build, fair skin", "hair": "long wavy lavender hair, hair covering one eye", diff --git a/nodes/character_reader/characters/chun_li.json b/nodes/character_reader/characters/chun_li.json index 2a76026..80de2c3 100644 --- a/nodes/character_reader/characters/chun_li.json +++ b/nodes/character_reader/characters/chun_li.json @@ -1,7 +1,7 @@ { "character_id": "chun_li", "identity": { - "base_specs": "1girl, muscular build, fair skin", + "base_specs": "1girl, muscular build, fair skin, asian", "hair": "black hair, hair buns", "eyes": "brown eyes", "expression": "determined smile", @@ -22,7 +22,7 @@ "accessories": "white hair ribbons, spiked bracelets" }, "styles": { - "aesthetic": "martial arts, traditional, street fighter style", + "aesthetic": "chinese style", "primary_color": "blue", "secondary_color": "white", "tertiary_color": "gold" diff --git a/nodes/character_reader/characters/ciri.json b/nodes/character_reader/characters/ciri.json index f1fde54..f12e67b 100644 --- a/nodes/character_reader/characters/ciri.json +++ b/nodes/character_reader/characters/ciri.json @@ -1,11 +1,11 @@ { "character_id": "ciri", "identity": { - "base_specs": "1girl, athletic build, pale skin", + "base_specs": "1girl, athletic build", "hair": "ashen grey hair, messy bun", - "eyes": "emerald green eyes. mascara", + "eyes": "emerald green eyes, mascara", "expression": "determined look", - "hands": "brown nails", + "hands": "green nails", "arms": "", "torso": "medium breasts", "pelvis": "", @@ -15,7 +15,7 @@ }, "wardrobe": { "inner_layer": "white blouse", - "outer_layer": "brown leather vest, silver studs", + "outer_layer": "", "lower_body": "brown leather trousers", "footwear": "brown leather boots", "gloves": "brown leather gloves", diff --git a/nodes/character_reader/characters/delinquent_mother_flim13.json b/nodes/character_reader/characters/delinquent_mother_flim13.json new file mode 100644 index 0000000..7dc00af --- /dev/null +++ b/nodes/character_reader/characters/delinquent_mother_flim13.json @@ -0,0 +1,35 @@ +{ + "character_id": "delinquent_mother_flim13", + "identity": { + "base_specs": "1girl, milf, gyaru, tall", + "hair": "blonde hair, long hair", + "eyes": "sharp eyes", + "expression": "smirk, sharp teeth", + "hands": "painted nails", + "arms": "", + "torso": "very large breasts", + "pelvis": "wide hips", + "legs": "", + "feet": "", + "distinguishing_marks": "" + }, + "wardrobe": { + "inner_layer": "biege sweater, cleavage", + "outer_layer": "", + "lower_body": "pencil skirt", + "footwear": "high heels", + "gloves": "", + "accessories": "necklace, rings" + }, + "styles": { + "aesthetic": "gyaru, milf, pink leopard print", + "primary_color": "pink", + "secondary_color": "black", + "tertiary_color": "gold" + }, + "lora": { + "lora_name": "Illustrious/Looks/Gyaru_mom_Flim13_IL_V1.safetensors", + "lora_weight": 1.0, + "lora_triggers": "" + } +} \ No newline at end of file diff --git a/nodes/character_reader/characters/gold_city.json b/nodes/character_reader/characters/gold_city.json new file mode 100644 index 0000000..6948bcf --- /dev/null +++ b/nodes/character_reader/characters/gold_city.json @@ -0,0 +1,35 @@ +{ + "character_id": "gold_city_(Umamusume)", + "identity": { + "base_specs": "1girl, horse ears, horse tail, tall", + "hair": "blonde hair, wavy hair", + "eyes": "blue eyes", + "expression": "confident expression", + "hands": "", + "arms": "", + "torso": "medium breasts", + "pelvis": "", + "legs": "", + "feet": "", + "distinguishing_marks": "" + }, + "wardrobe": { + "inner_layer": "white shirt", + "outer_layer": "tracen school uniform", + "lower_body": "pleated skirt", + "footwear": "heeled shoes", + "gloves": "", + "accessories": "choker, earrings" + }, + "styles": { + "aesthetic": "fashionable, model", + "primary_color": "gold", + "secondary_color": "white", + "tertiary_color": "black" + }, + "lora": { + "lora_name": "", + "lora_weight": 1.0, + "lora_triggers": "" + } +} \ No newline at end of file diff --git a/nodes/character_reader/characters/gold_ship.json b/nodes/character_reader/characters/gold_ship.json new file mode 100644 index 0000000..7d79611 --- /dev/null +++ b/nodes/character_reader/characters/gold_ship.json @@ -0,0 +1,35 @@ +{ + "character_id": "gold_ship_(Umamusume)", + "identity": { + "base_specs": "1girl, horse ears, horse tail, tall", + "hair": "grey hair, short hair", + "eyes": "red eyes", + "expression": "crazy expression, grin", + "hands": "", + "arms": "", + "torso": "medium breasts", + "pelvis": "", + "legs": "", + "feet": "", + "distinguishing_marks": "" + }, + "wardrobe": { + "inner_layer": "white shirt", + "outer_layer": "tracen school uniform", + "lower_body": "pleated skirt", + "footwear": "heeled shoes", + "gloves": "", + "accessories": "ear covers, hat" + }, + "styles": { + "aesthetic": "energetic, sporty", + "primary_color": "red", + "secondary_color": "white", + "tertiary_color": "gold" + }, + "lora": { + "lora_name": "", + "lora_weight": 1.0, + "lora_triggers": "" + } +} \ No newline at end of file diff --git a/nodes/character_reader/characters/jessica_rabbit.json b/nodes/character_reader/characters/jessica_rabbit.json new file mode 100644 index 0000000..3554cae --- /dev/null +++ b/nodes/character_reader/characters/jessica_rabbit.json @@ -0,0 +1,35 @@ +{ + "character_id": "jessica_rabbit", + "identity": { + "base_specs": "1girl, voluptuous build, tall,", + "hair": "long red hair, side part, hair over one eye", + "eyes": "green eyes, heavy makeup, purple eyeshadow", + "expression": "seductive smile", + "hands": "purple elbow gloves", + "arms": "", + "torso": "large breasts", + "pelvis": "narrow waist", + "legs": "", + "feet": "", + "distinguishing_marks": "red lips" + }, + "wardrobe": { + "inner_layer": "", + "outer_layer": "red sequin dress, strapless, high slit, backless", + "lower_body": "side_slit,", + "footwear": "red high heels", + "gloves": "purple opera gloves", + "accessories": "gold earrings, glitter" + }, + "styles": { + "aesthetic": "noir, cartoon, glamorous", + "primary_color": "red", + "secondary_color": "purple", + "tertiary_color": "gold" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + } +} \ No newline at end of file diff --git a/nodes/character_reader/characters/jessie.json b/nodes/character_reader/characters/jessie.json index 35a9e7a..d77bb64 100644 --- a/nodes/character_reader/characters/jessie.json +++ b/nodes/character_reader/characters/jessie.json @@ -18,8 +18,8 @@ "outer_layer": "white Team Rocket uniform jacket, bare stomach, red R logo", "lower_body": "white miniskirt", "footwear": "black thigh-high boots", - "gloves": "black gloves", - "accessories": "" + "gloves": "black elbow gloves", + "accessories": "green earrings" }, "styles": { "aesthetic": "villainous, anime, pokemon style", diff --git a/nodes/character_reader/characters/jinx.json b/nodes/character_reader/characters/jinx.json new file mode 100644 index 0000000..c81348e --- /dev/null +++ b/nodes/character_reader/characters/jinx.json @@ -0,0 +1,35 @@ +{ + "character_id": "jinx", + "identity": { + "base_specs": "1girl, slender build, pale skin,", + "hair": "long aqua hair, twin braids, very long hair, bangs", + "eyes": "pink eyes, ", + "expression": "crazy eyes, manic grin, crazy smile", + "hands": "black and pink nails", + "arms": "", + "torso": "flat chest,", + "pelvis": "", + "legs": "", + "feet": "", + "distinguishing_marks": "cloud tattoo," + }, + "wardrobe": { + "inner_layer": "", + "outer_layer": "pink and black bikini, asymmetrical_bikini ", + "lower_body": "pink shorts, single pink stocking", + "footwear": "combat boots", + "gloves": "black fingerless gloves, fishnet elbow gloves,", + "accessories": "ammo belts, choker, bullet necklace," + }, + "styles": { + "aesthetic": "punk, chaotic,", + "primary_color": "pink", + "secondary_color": "black", + "tertiary_color": "aqua" + }, + "lora": { + "lora_name": "Illustrious/Looks/jinx_default_lol-000021.safetensors", + "lora_weight": 0.8, + "lora_triggers": "" + } +} \ No newline at end of file diff --git a/nodes/character_reader/characters/kagamine_rin.json b/nodes/character_reader/characters/kagamine_rin.json new file mode 100644 index 0000000..d586d1a --- /dev/null +++ b/nodes/character_reader/characters/kagamine_rin.json @@ -0,0 +1,35 @@ +{ + "character_id": "kagamine_rin", + "identity": { + "base_specs": "1girl, petite", + "hair": "blonde hair, short hair, hair bow", + "eyes": "blue eyes", + "expression": "smile, energetic", + "hands": "", + "arms": "detached sleeves", + "torso": "flat chest", + "pelvis": "", + "legs": "leg warmers", + "feet": "", + "distinguishing_marks": "" + }, + "wardrobe": { + "inner_layer": "white shirt, sailor collar", + "outer_layer": "", + "lower_body": "black shorts, yellow belt", + "footwear": "white shoes", + "gloves": "", + "accessories": "headset, hair bow" + }, + "styles": { + "aesthetic": "vocaloid, cyber", + "primary_color": "yellow", + "secondary_color": "white", + "tertiary_color": "black" + }, + "lora": { + "lora_name": "", + "lora_weight": 1.0, + "lora_triggers": "" + } +} \ No newline at end of file diff --git a/nodes/character_reader/characters/kda_all_out_ahri.json b/nodes/character_reader/characters/kda_all_out_ahri.json index a32e8f0..1c17f93 100644 --- a/nodes/character_reader/characters/kda_all_out_ahri.json +++ b/nodes/character_reader/characters/kda_all_out_ahri.json @@ -19,7 +19,7 @@ "lower_body": "black leather shorts", "footwear": "black thigh-high boots", "gloves": "", - "accessories": "crystal orb, silver jewelry" + "accessories": "crystal heart, silver jewelry" }, "styles": { "aesthetic": "pop star, mystical, k/da style", diff --git a/nodes/character_reader/characters/kda_all_out_evelynn.json b/nodes/character_reader/characters/kda_all_out_evelynn.json index 71dcfb6..2812a9b 100644 --- a/nodes/character_reader/characters/kda_all_out_evelynn.json +++ b/nodes/character_reader/characters/kda_all_out_evelynn.json @@ -5,7 +5,7 @@ "hair": "light blue hair,", "eyes": "yellow glowing eyes, slit pupils", "expression": "seductive, confident look", - "hands": "long black claws, blue nails", + "hands": "metal claws", "arms": "", "torso": "medium breasts", "pelvis": "", diff --git a/nodes/character_reader/characters/kda_all_out_kaisa.json b/nodes/character_reader/characters/kda_all_out_kaisa.json index 1e52a33..4109790 100644 --- a/nodes/character_reader/characters/kda_all_out_kaisa.json +++ b/nodes/character_reader/characters/kda_all_out_kaisa.json @@ -11,7 +11,7 @@ "pelvis": "", "legs": "", "feet": "", - "distinguishing_marks": "purple markings under eyes, floating crystal cannons" + "distinguishing_marks": "" }, "wardrobe": { "inner_layer": "silver bodysuit", diff --git a/nodes/character_reader/characters/komi_shouko.json b/nodes/character_reader/characters/komi_shouko.json new file mode 100644 index 0000000..6d4c2f6 --- /dev/null +++ b/nodes/character_reader/characters/komi_shouko.json @@ -0,0 +1,35 @@ +{ + "character_id": "komi_shouko", + "identity": { + "base_specs": "1girl, slender build, pale skin, asian", + "hair": "long dark purple hair, hime cut,", + "eyes": "dark purple eyes,", + "expression": "neutral expression, stoic, cat ears", + "hands": "", + "arms": "", + "torso": "medium breasts", + "pelvis": "", + "legs": "black pantyhose", + "feet": "", + "distinguishing_marks": "" + }, + "wardrobe": { + "inner_layer": "white shirt", + "outer_layer": "itan private high school uniform, blazer, striped bow tie", + "lower_body": "plaid skirt", + "footwear": "loafers", + "gloves": "", + "accessories": "" + }, + "styles": { + "aesthetic": "anime, manga, clean lines", + "primary_color": "purple", + "secondary_color": "magenta", + "tertiary_color": "white" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "komi shouko, itan private high school uniform" + } +} \ No newline at end of file diff --git a/nodes/character_reader/characters/lara_croft_classic.json b/nodes/character_reader/characters/lara_croft_classic.json index 61a48ec..cc3e6d9 100644 --- a/nodes/character_reader/characters/lara_croft_classic.json +++ b/nodes/character_reader/characters/lara_croft_classic.json @@ -1,13 +1,13 @@ { "character_id": "lara_croft_classic", "identity": { - "base_specs": "1girl, athletic build, tan skin", + "base_specs": "1girl, athletic build,", "hair": "long brown hair, single braid", "eyes": "brown eyes", - "expression": "determined", + "expression": "light smile, raised eyebrow", "hands": "", "arms": "", - "torso": "", + "torso": "large breasts", "pelvis": "", "legs": "", "feet": "", @@ -15,11 +15,11 @@ }, "wardrobe": { "inner_layer": "", - "outer_layer": "teal tank top, crop top", + "outer_layer": "teal tank top,", "lower_body": "brown shorts", "footwear": "brown combat boots, red laces", "gloves": "black fingerless gloves", - "accessories": "dual thigh holsters, backpack, circular sunglasses" + "accessories": "dual thigh pistol holsters, brown leatherbackpack, red circular sunglasses" }, "styles": { "aesthetic": "adventure, retro, 90s style", @@ -28,8 +28,8 @@ "tertiary_color": "black" }, "lora": { - "lora_name": "lara_croft_classic", + "lora_name": "Illustrious/Looks/LaraCroft_ClassicV2_Illu_Dwnsty.safetensors", "lora_weight": 0.8, - "lora_triggers": "lara croft, classic outfit" + "lora_triggers": "" } } \ No newline at end of file diff --git a/nodes/character_reader/characters/lisa_minci.json b/nodes/character_reader/characters/lisa_minci.json new file mode 100644 index 0000000..23c6e5d --- /dev/null +++ b/nodes/character_reader/characters/lisa_minci.json @@ -0,0 +1,35 @@ +{ + "character_id": "lisa_(genshin_impact)", + "identity": { + "base_specs": "1girl, tall, mature female", + "hair": "brown hair, wavy hair, side ponytail", + "eyes": "green eyes", + "expression": "seductive smile", + "hands": "", + "arms": "detached sleeves", + "torso": "large breasts", + "pelvis": "wide hips", + "legs": "black pantyhose", + "feet": "", + "distinguishing_marks": "beauty mark" + }, + "wardrobe": { + "inner_layer": "purple dress, corset", + "outer_layer": "purple shawl", + "lower_body": "slit skirt", + "footwear": "black heels", + "gloves": "purple gloves", + "accessories": "witch hat, rose, necklace" + }, + "styles": { + "aesthetic": "genshin impact, witch, librarian", + "primary_color": "purple", + "secondary_color": "white", + "tertiary_color": "gold" + }, + "lora": { + "lora_name": "", + "lora_weight": 1.0, + "lora_triggers": "" + } +} \ No newline at end of file diff --git a/nodes/character_reader/characters/lulu.json b/nodes/character_reader/characters/lulu.json index 6663471..57d6793 100644 --- a/nodes/character_reader/characters/lulu.json +++ b/nodes/character_reader/characters/lulu.json @@ -1,10 +1,10 @@ { - "character_id": "lulu (ffx)", + "character_id": "lulu (ff10)", "identity": { "base_specs": "1girl, curvaceous build, fair skin", "hair": "long black hair, complex braids, hairpins", "eyes": "red eyes", - "expression": "stern expression", + "expression": "thinking, raised eyebrow", "hands": "black nails", "arms": "", "torso": "large breasts", diff --git a/nodes/character_reader/characters/majin_android_21.json b/nodes/character_reader/characters/majin_android_21.json index b044fdf..eb9ebc9 100644 --- a/nodes/character_reader/characters/majin_android_21.json +++ b/nodes/character_reader/characters/majin_android_21.json @@ -4,22 +4,22 @@ "base_specs": "1girl, curvaceous build, pink skin", "hair": "long voluminous white hair", "eyes": "red eyes, black sclera", - "expression": "playful yet menacing smile", + "expression": "evil smile", "hands": "black claws, pink nails", "arms": "", - "torso": "medium breasts", + "torso": "large breasts", "pelvis": "", "legs": "", "feet": "", - "distinguishing_marks": "pink skin, long tail, pointed ears" + "distinguishing_marks": "pink skin, long tail, pointy ears" }, "wardrobe": { "inner_layer": "black tube top", "outer_layer": "", - "lower_body": "white baggy pants", + "lower_body": "white harem pants", "footwear": "black and yellow boots", - "gloves": "", - "accessories": "gold bracelets, gold neck ring" + "gloves": "black sleeves", + "accessories": "gold bracelets, gold neck ring, hoop earrings" }, "styles": { "aesthetic": "supernatural, anime, dragon ball style", diff --git a/nodes/character_reader/characters/marin_kitagawa.json b/nodes/character_reader/characters/marin_kitagawa.json index 8cf087a..2809e5a 100644 --- a/nodes/character_reader/characters/marin_kitagawa.json +++ b/nodes/character_reader/characters/marin_kitagawa.json @@ -1,7 +1,7 @@ { "character_id": "marin_kitagawa", "identity": { - "base_specs": "1girl, slender build, fair skin", + "base_specs": "1girl, slender build, fair skin, asian", "hair": "long blonde hair, pink tips", "eyes": "pink eyes (contacts)", "expression": "excited smile", diff --git a/nodes/character_reader/characters/megurine_luka.json b/nodes/character_reader/characters/megurine_luka.json new file mode 100644 index 0000000..ad49ee5 --- /dev/null +++ b/nodes/character_reader/characters/megurine_luka.json @@ -0,0 +1,35 @@ +{ + "character_id": "megurine_luka", + "identity": { + "base_specs": "1girl, tall, mature female", + "hair": "pink hair, long hair", + "eyes": "blue eyes", + "expression": "light smile", + "hands": "", + "arms": "", + "torso": "medium breasts", + "pelvis": "", + "legs": "", + "feet": "", + "distinguishing_marks": "" + }, + "wardrobe": { + "inner_layer": "", + "outer_layer": "crop top, detached sleeves, gold trim", + "lower_body": "side slit, lace-up skirt", + "footwear": "thinghighs, lace-up boots, gold boots, gold armlet", + "gloves": "", + "accessories": "headset" + }, + "styles": { + "aesthetic": "vocaloid, elegant", + "primary_color": "black", + "secondary_color": "gold", + "tertiary_color": "pink" + }, + "lora": { + "lora_name": "", + "lora_weight": 1.0, + "lora_triggers": "" + } +} \ No newline at end of file diff --git a/nodes/character_reader/characters/meiko.json b/nodes/character_reader/characters/meiko.json new file mode 100644 index 0000000..b051683 --- /dev/null +++ b/nodes/character_reader/characters/meiko.json @@ -0,0 +1,35 @@ +{ + "character_id": "meiko", + "identity": { + "base_specs": "1girl, mature female", + "hair": "brown hair, short hair", + "eyes": "brown eyes", + "expression": "smile, confident", + "hands": "", + "arms": "", + "torso": "medium breasts", + "pelvis": "", + "legs": "", + "feet": "", + "distinguishing_marks": "" + }, + "wardrobe": { + "inner_layer": "red crop top, sleeveless", + "outer_layer": "", + "lower_body": "red skirt, mini skirt", + "footwear": "brown boots", + "gloves": "", + "accessories": "choker" + }, + "styles": { + "aesthetic": "vocaloid, casual", + "primary_color": "red", + "secondary_color": "brown", + "tertiary_color": "black" + }, + "lora": { + "lora_name": "", + "lora_weight": 1.0, + "lora_triggers": "" + } +} \ No newline at end of file diff --git a/nodes/character_reader/characters/olivier_mira_armstrong.json b/nodes/character_reader/characters/olivier_mira_armstrong.json new file mode 100644 index 0000000..532a33b --- /dev/null +++ b/nodes/character_reader/characters/olivier_mira_armstrong.json @@ -0,0 +1,35 @@ +{ + "character_id": "olivier_mira_armstrong", + "identity": { + "base_specs": "1girl, tall, mature female", + "hair": "blonde hair, long hair, hair over one eye", + "eyes": "blue eyes, sharp eyes", + "expression": "serious", + "hands": "", + "arms": "", + "torso": "medium breasts", + "pelvis": "", + "legs": "", + "feet": "", + "distinguishing_marks": "thick lips" + }, + "wardrobe": { + "inner_layer": "black shirt", + "outer_layer": "blue military coat, fur collar", + "lower_body": "black pants", + "footwear": "black boots", + "gloves": "black gloves", + "accessories": "sword" + }, + "styles": { + "aesthetic": "military, amestris uniform", + "primary_color": "blue", + "secondary_color": "black", + "tertiary_color": "gold" + }, + "lora": { + "lora_name": "", + "lora_weight": 1.0, + "lora_triggers": "" + } +} \ No newline at end of file diff --git a/nodes/character_reader/characters/princess_peach.json b/nodes/character_reader/characters/princess_peach.json index 4f8092e..6ec2878 100644 --- a/nodes/character_reader/characters/princess_peach.json +++ b/nodes/character_reader/characters/princess_peach.json @@ -5,7 +5,7 @@ "hair": "long blonde hair, voluminous, crown", "eyes": "blue eyes, long eyelashes", "expression": "gentle smile", - "hands": "pink nails", + "hands": "", "arms": "", "torso": "medium breasts", "pelvis": "", diff --git a/nodes/character_reader/characters/princess_zelda_botw.json b/nodes/character_reader/characters/princess_zelda_botw.json index 1b2cdf3..c7210ba 100644 --- a/nodes/character_reader/characters/princess_zelda_botw.json +++ b/nodes/character_reader/characters/princess_zelda_botw.json @@ -4,7 +4,7 @@ "base_specs": "1girl, slender build, fair skin, pointed ears", "hair": "long blonde hair, braided, gold hair clips", "eyes": "green eyes", - "expression": "determined look", + "expression": "curious", "hands": "gold nails", "arms": "", "torso": "small breasts", diff --git a/nodes/character_reader/characters/rice_shower.json b/nodes/character_reader/characters/rice_shower.json new file mode 100644 index 0000000..369091b --- /dev/null +++ b/nodes/character_reader/characters/rice_shower.json @@ -0,0 +1,35 @@ +{ + "character_id": "rice_shower_(Umamusume)", + "identity": { + "base_specs": "1girl, petite, horse ears, horse tail", + "hair": "long dark brown hair, bangs, hair over one eye", + "eyes": "purple eyes", + "expression": "shy expression", + "hands": "", + "arms": "", + "torso": "small breasts", + "pelvis": "", + "legs": "", + "feet": "", + "distinguishing_marks": "" + }, + "wardrobe": { + "inner_layer": "white shirt", + "outer_layer": "tracen school uniform", + "lower_body": "pleated skirt", + "footwear": "heeled shoes", + "gloves": "", + "accessories": "blue rose, hair flower, small hat, dagger" + }, + "styles": { + "aesthetic": "gothic lolita, elegant", + "primary_color": "purple", + "secondary_color": "blue", + "tertiary_color": "black" + }, + "lora": { + "lora_name": "", + "lora_weight": 1.0, + "lora_triggers": "" + } +} \ No newline at end of file diff --git a/nodes/character_reader/characters/riju.json b/nodes/character_reader/characters/riju.json index 7a47f0f..ea6caab 100644 --- a/nodes/character_reader/characters/riju.json +++ b/nodes/character_reader/characters/riju.json @@ -11,25 +11,25 @@ "pelvis": "", "legs": "", "feet": "", - "distinguishing_marks": "blue lipstick,gerudo markings" + "distinguishing_marks": "darkblue lipstick," }, "wardrobe": { "inner_layer": "", - "outer_layer": "gerudo top, colorful sash", - "lower_body": "gerudo pants, puffy pants", - "footwear": "sandals", + "outer_layer": "black top, blue sash", + "lower_body": "black skirt, pelvic curtain,", + "footwear": "gold high heels", "gloves": "", - "accessories": "gold jewelry, crown, earrings" + "accessories": "gold jewelry, earrings" }, "styles": { "aesthetic": "fantasy, desert, gerudo style", "primary_color": "gold", - "secondary_color": "teal", + "secondary_color": "black", "tertiary_color": "red" }, "lora": { - "lora_name": "riju_botw", + "lora_name": "", "lora_weight": 0.8, - "lora_triggers": "riju, gerudo" + "lora_triggers": "" } } \ No newline at end of file diff --git a/nodes/character_reader/characters/rouge_the_bat.json b/nodes/character_reader/characters/rouge_the_bat.json index ae41893..2f8e389 100644 --- a/nodes/character_reader/characters/rouge_the_bat.json +++ b/nodes/character_reader/characters/rouge_the_bat.json @@ -15,21 +15,21 @@ }, "wardrobe": { "inner_layer": "", - "outer_layer": "black skin-tight jumpsuit, pink heart-shaped chest plate", + "outer_layer": "black skin-tight jumpsuit, pink heart-shaped chest plate, bare shoulders, cleavage", "lower_body": "jumpsuit", "footwear": "white boots, pink heart motifs", "gloves": "white gloves, pink cuffs", "accessories": "blue eyeshadow" }, "styles": { - "aesthetic": "sleek, spy, sonic style", + "aesthetic": "jewels, museum,sleek, spy, sonic style", "primary_color": "white", "secondary_color": "pink", "tertiary_color": "black" }, "lora": { - "lora_name": "", - "lora_weight": 1.0, + "lora_name": "Illustrious/Looks/Rouge_the_bat_v2.safetensors", + "lora_weight": 0.8, "lora_triggers": "" } } \ No newline at end of file diff --git a/nodes/character_reader/characters/ryouko_hakubi.json b/nodes/character_reader/characters/ryouko_hakubi.json new file mode 100644 index 0000000..32b772e --- /dev/null +++ b/nodes/character_reader/characters/ryouko_hakubi.json @@ -0,0 +1,35 @@ +{ + "character_id": "ryouko_(tenchi_muyou!)", + "identity": { + "base_specs": "1girl, slim build,", + "hair": "long teal hair, spiky, voluminous", + "eyes": "golden eyes, cat-like pupils", + "expression": "confident smirk", + "hands": "", + "arms": "", + "torso": "medium breasts", + "pelvis": "", + "legs": "", + "feet": "", + "distinguishing_marks": "red gem on forehead," + }, + "wardrobe": { + "inner_layer": "long white dress, plunging neckline, black belt", + "outer_layer": "black and orange long sleeve jacket with purple trim,", + "lower_body": "side_slit,, red trousers", + "footwear": "", + "gloves": "red gloves", + "accessories": "red gems, wristbands" + }, + "styles": { + "aesthetic": "90s anime, sci-fi", + "primary_color": "teal", + "secondary_color": "white", + "tertiary_color": "red" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "ryouko hakubi, space pirate" + } +} \ No newline at end of file diff --git a/nodes/character_reader/characters/sarah_miller.json b/nodes/character_reader/characters/sarah_miller.json new file mode 100644 index 0000000..e0202a6 --- /dev/null +++ b/nodes/character_reader/characters/sarah_miller.json @@ -0,0 +1,35 @@ +{ + "character_id": "sarah_miller_(the_last_of_us)", + "identity": { + "base_specs": "1girl, loli, small build", + "hair": "blonde hair, short hair", + "eyes": "blue eyes", + "expression": "smile", + "hands": "", + "arms": "", + "torso": "flat chest", + "pelvis": "", + "legs": "", + "feet": "", + "distinguishing_marks": "" + }, + "wardrobe": { + "inner_layer": "grey t-shirt, white shirt", + "outer_layer": "", + "lower_body": "blue jeans", + "footwear": "sneakers", + "gloves": "", + "accessories": "wristwatch" + }, + "styles": { + "aesthetic": "casual, 2013 fashion", + "primary_color": "grey", + "secondary_color": "blue", + "tertiary_color": "white" + }, + "lora": { + "lora_name": "", + "lora_weight": 1.0, + "lora_triggers": "" + } +} \ No newline at end of file diff --git a/nodes/character_reader/characters/shantae.json b/nodes/character_reader/characters/shantae.json new file mode 100644 index 0000000..43b2df5 --- /dev/null +++ b/nodes/character_reader/characters/shantae.json @@ -0,0 +1,35 @@ +{ + "character_id": "shantae", + "identity": { + "base_specs": "1girl, dark skin, pointy ears", + "hair": "purple hair, very long hair, ponytail", + "eyes": "blue eyes", + "expression": "smile, energetic", + "hands": "", + "arms": "gold bracelets", + "torso": "small breasts, perky breasts", + "pelvis": "wide hips", + "legs": "", + "feet": "", + "distinguishing_marks": "" + }, + "wardrobe": { + "inner_layer": "", + "outer_layer": "red bikini top, red harem pants, gold trim", + "lower_body": "", + "footwear": "gold shoes", + "gloves": "", + "accessories": "gold tiara, hoop earrings" + }, + "styles": { + "aesthetic": "genie, dancer, arabian", + "primary_color": "red", + "secondary_color": "gold", + "tertiary_color": "purple" + }, + "lora": { + "lora_name": "", + "lora_weight": 1.0, + "lora_triggers": "" + } +} \ No newline at end of file diff --git a/nodes/character_reader/characters/sucy_manbavaran.json b/nodes/character_reader/characters/sucy_manbavaran.json index 2bae0d1..ec26f02 100644 --- a/nodes/character_reader/characters/sucy_manbavaran.json +++ b/nodes/character_reader/characters/sucy_manbavaran.json @@ -2,13 +2,13 @@ "character_id": "sucy_manbavaran", "identity": { "base_specs": "1girl, lanky build, pale skin", - "hair": "mauve hair, hair covering one eye", - "eyes": "droopy red eyes", + "hair": "light purple hair, hair covering one eye", + "eyes": "red eyes", "expression": "deadpan expression", - "hands": "purple nails", + "hands": "black nails", "arms": "", "torso": "small breasts", - "pelvis": "", + "pelvis": "narrow waist", "legs": "", "feet": "", "distinguishing_marks": "dark circles under eyes" @@ -16,7 +16,7 @@ "wardrobe": { "inner_layer": "", "outer_layer": "dark purple witch robes", - "lower_body": "long skirt", + "lower_body": "long skirt with frayed edges", "footwear": "brown boots", "gloves": "", "accessories": "pointed witch hat, potion bottle" diff --git a/nodes/character_reader/characters/urbosa.json b/nodes/character_reader/characters/urbosa.json index ee5ef92..3bd37ec 100644 --- a/nodes/character_reader/characters/urbosa.json +++ b/nodes/character_reader/characters/urbosa.json @@ -7,16 +7,16 @@ "expression": "confident", "hands": "gold nails", "arms": "muscular arms", - "torso": "abs", + "torso": "abs, mediumS breasts", "pelvis": "wide hips", "legs": "muscular legs", "feet": "", - "distinguishing_marks": "blue lipstick, gerudo markings" + "distinguishing_marks": "dark blue lipstick, gerudo markings" }, "wardrobe": { "inner_layer": "", - "outer_layer": "gold breastplate, blue champion's skirt", - "lower_body": "", + "outer_layer": "blue top, blue champion's skirt, green sash, green shoulder guards,", + "lower_body": "blue skirt", "footwear": "gold heels", "gloves": "", "accessories": "gold jewelry, scimitar" @@ -28,8 +28,8 @@ "tertiary_color": "red" }, "lora": { - "lora_name": "urbosa_botw", + "lora_name": "", "lora_weight": 0.8, - "lora_triggers": "urbosa, gerudo" + "lora_triggers": "" } } \ No newline at end of file diff --git a/nodes/character_reader/characters/yshtola_rhul.json b/nodes/character_reader/characters/yshtola_rhul.json index 0a9d268..d5e3d99 100644 --- a/nodes/character_reader/characters/yshtola_rhul.json +++ b/nodes/character_reader/characters/yshtola_rhul.json @@ -3,7 +3,7 @@ "identity": { "base_specs": "1girl, miqo'te, slender build, fair skin, cat ears", "hair": "short white hair, bangs", - "eyes": "blind white eyes", + "eyes": "blind, white eyes", "expression": "stoic expression", "hands": "black nails", "arms": "", diff --git a/nodes/character_reader/characters/yuna_ffx.json b/nodes/character_reader/characters/yuna_ffx.json index ee90551..cd2cb1f 100644 --- a/nodes/character_reader/characters/yuna_ffx.json +++ b/nodes/character_reader/characters/yuna_ffx.json @@ -1,5 +1,5 @@ { - "character_id": "yuna_ffx", + "character_id": "yuna_(ff10)", "identity": { "base_specs": "1girl, slender, fair skin", "hair": "short brown hair, bob cut", @@ -28,8 +28,8 @@ "tertiary_color": "yellow" }, "lora": { - "lora_name": "yuna_ffx", + "lora_name": "", "lora_weight": 0.8, - "lora_triggers": "yuna, summoner outfit" + "lora_triggers": "" } } \ No newline at end of file diff --git a/nodes/checkpoint_from_string/__init__.py b/nodes/checkpoint_from_string/__init__.py new file mode 100644 index 0000000..be7e4e9 --- /dev/null +++ b/nodes/checkpoint_from_string/__init__.py @@ -0,0 +1,9 @@ +from .checkpoint_from_string import CheckpointLoaderFromString + +NODE_CLASS_MAPPINGS = { + "CheckpointLoaderFromString": CheckpointLoaderFromString +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "CheckpointLoaderFromString": "Checkpoint Loader (From String)" +} diff --git a/nodes/checkpoint_from_string/checkpoint_from_string.py b/nodes/checkpoint_from_string/checkpoint_from_string.py new file mode 100644 index 0000000..794cc9b --- /dev/null +++ b/nodes/checkpoint_from_string/checkpoint_from_string.py @@ -0,0 +1,19 @@ +import folder_paths +import comfy.sd + +class CheckpointLoaderFromString: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "ckpt_name": ("STRING", {"multiline": False, "forceInput": True}), + } + } + RETURN_TYPES = ("MODEL", "CLIP", "VAE") + FUNCTION = "load_checkpoint" + CATEGORY = "AODH Pack" + + def load_checkpoint(self, ckpt_name): + ckpt_path = folder_paths.get_full_path_or_raise("checkpoints", ckpt_name) + out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings")) + return out[:3] diff --git a/nodes/checkpoint_selector/__init__.py b/nodes/checkpoint_selector/__init__.py new file mode 100644 index 0000000..8c9f8a8 --- /dev/null +++ b/nodes/checkpoint_selector/__init__.py @@ -0,0 +1,9 @@ +from .checkpoint_selector import CheckpointSelector + +NODE_CLASS_MAPPINGS = { + "CheckpointSelector": CheckpointSelector +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "CheckpointSelector": "Checkpoint Selector" +} diff --git a/nodes/checkpoint_selector/checkpoint_selector.py b/nodes/checkpoint_selector/checkpoint_selector.py new file mode 100644 index 0000000..36548ba --- /dev/null +++ b/nodes/checkpoint_selector/checkpoint_selector.py @@ -0,0 +1,120 @@ +import os +import random +import folder_paths + +class CheckpointSelector: + def __init__(self): + self.current_index = 0 + self.current_count = 0 + self.last_folder = None + self.last_selection = None + self.last_manual_index = 0 + + @classmethod + def INPUT_TYPES(s): + # Find folders in 'checkpoints' paths + checkpoint_paths = folder_paths.get_folder_paths("checkpoints") + checkpoint_folders = [] + + for path in checkpoint_paths: + if os.path.exists(path) and os.path.isdir(path): + # List subdirectories + try: + subdirs = [d for d in os.listdir(path) if os.path.isdir(os.path.join(path, d))] + checkpoint_folders.extend(subdirs) + except Exception: + pass + + # Remove duplicates and sort + checkpoint_folders = sorted(list(set(checkpoint_folders))) + + if not checkpoint_folders: + checkpoint_folders = ["None"] + + return { + "required": { + "folder": (checkpoint_folders, ), + "mode": (["Random", "Sequential", "Manual"], {"default": "Random"}), + "repeat_count": ("INT", {"default": 1, "min": 1, "max": 100, "step": 1}), + "manual_index": ("INT", {"default": 0, "min": 0, "max": 10000, "step": 1}), + } + } + + RETURN_TYPES = ("STRING", "INT") + RETURN_NAMES = ("checkpoint_name", "total_checkpoints") + FUNCTION = "select_checkpoint" + CATEGORY = "AODH Pack" + + def select_checkpoint(self, folder, mode, repeat_count, manual_index): + repeat_count = max(1, repeat_count) + if folder == "None": + return ("", 0) + + # Find the full path for the selected folder + checkpoint_paths = folder_paths.get_folder_paths("checkpoints") + target_path = None + + for path in checkpoint_paths: + check_path = os.path.join(path, folder) + if os.path.exists(check_path) and os.path.isdir(check_path): + target_path = check_path + break + + if not target_path: + return ("", 0) + + # Find checkpoint files (.safetensors, .ckpt) + extensions = ('.safetensors', '.ckpt') + checkpoint_files = [f for f in os.listdir(target_path) if f.lower().endswith(extensions)] + checkpoint_files.sort() + + count = len(checkpoint_files) + if count == 0: + return ("", 0) + + selected_checkpoint = "" + + # Reset state if folder changes + if self.last_folder != folder: + self.current_index = 0 + self.current_count = 0 + self.last_selection = None + self.last_folder = folder + + # Reset state if manual_index changes (for Sequential mode) + if self.last_manual_index != manual_index: + self.current_index = 0 + self.current_count = 0 + self.last_manual_index = manual_index + + if mode == "Random": + if self.current_count >= repeat_count or self.last_selection is None: + rng = random.Random() + self.last_selection = rng.choice(checkpoint_files) + self.current_count = 0 + + selected_checkpoint = self.last_selection + self.current_count += 1 + + elif mode == "Sequential": + if self.current_count >= repeat_count: + self.current_index += 1 + self.current_count = 0 + + selected_checkpoint = checkpoint_files[(self.current_index + manual_index) % count] + self.current_count += 1 + + elif mode == "Manual": + selected_checkpoint = checkpoint_files[manual_index % count] + + # Construct the relative path for ComfyUI Checkpoint loader + # Use forward slashes for consistency with ComfyUI paths + full_checkpoint_name = f"{folder}/{selected_checkpoint}" + + return (full_checkpoint_name, count) + + @classmethod + def IS_CHANGED(s, folder, mode, repeat_count, manual_index): + if mode == "Random" or mode == "Sequential": + return float("nan") + return f"{folder}_{manual_index}" diff --git a/nodes/lora_selector/__init__.py b/nodes/lora_selector/__init__.py new file mode 100644 index 0000000..e435cfa --- /dev/null +++ b/nodes/lora_selector/__init__.py @@ -0,0 +1,9 @@ +from .lora_selector import LoraSelector + +NODE_CLASS_MAPPINGS = { + "LoraSelector": LoraSelector +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "LoraSelector": "Lora Selector" +} diff --git a/nodes/lora_selector/lora_selector.py b/nodes/lora_selector/lora_selector.py new file mode 100644 index 0000000..d34855d --- /dev/null +++ b/nodes/lora_selector/lora_selector.py @@ -0,0 +1,121 @@ +import os +import random +import folder_paths + +class LoraSelector: + def __init__(self): + self.current_index = 0 + self.current_count = 0 + self.last_folder = None + self.last_selection = None + self.last_manual_index = 0 + + @classmethod + def INPUT_TYPES(s): + # Find 'Illustrious' folders in all lora paths + lora_paths = folder_paths.get_folder_paths("loras") + illustrious_folders = [] + + for path in lora_paths: + illustrious_path = os.path.join(path, "Illustrious") + if os.path.exists(illustrious_path) and os.path.isdir(illustrious_path): + # List subdirectories + try: + subdirs = [d for d in os.listdir(illustrious_path) if os.path.isdir(os.path.join(illustrious_path, d))] + illustrious_folders.extend(subdirs) + except Exception: + pass + + # Remove duplicates and sort + illustrious_folders = sorted(list(set(illustrious_folders))) + + if not illustrious_folders: + illustrious_folders = ["None"] + + return { + "required": { + "folder": (illustrious_folders, ), + "mode": (["Random", "Sequential", "Manual"], {"default": "Random"}), + "repeat_count": ("INT", {"default": 1, "min": 1, "max": 100, "step": 1}), + "manual_index": ("INT", {"default": 0, "min": 0, "max": 10000, "step": 1}), + } + } + + RETURN_TYPES = ("STRING", "INT") + RETURN_NAMES = ("lora_name", "total_loras") + FUNCTION = "select_lora" + CATEGORY = "AODH Pack" + + def select_lora(self, folder, mode, repeat_count, manual_index): + repeat_count = max(1, repeat_count) + if folder == "None": + return ("", 0) + + # Find the full path for the selected folder + lora_paths = folder_paths.get_folder_paths("loras") + target_path = None + + for path in lora_paths: + check_path = os.path.join(path, "Illustrious", folder) + if os.path.exists(check_path) and os.path.isdir(check_path): + target_path = check_path + break + + if not target_path: + return ("", 0) + + # Find .safetensors files + lora_files = [f for f in os.listdir(target_path) if f.endswith('.safetensors')] + lora_files.sort() # Ensure consistent order + + count = len(lora_files) + if count == 0: + return ("", 0) + + selected_lora = "" + + # Reset state if folder changes + if self.last_folder != folder: + self.current_index = 0 + self.current_count = 0 + self.last_selection = None + self.last_folder = folder + + # Reset state if manual_index changes (for Sequential mode) + if self.last_manual_index != manual_index: + self.current_index = 0 + self.current_count = 0 + self.last_manual_index = manual_index + + if mode == "Random": + if self.current_count >= repeat_count or self.last_selection is None: + # Use a local Random instance to ensure randomness regardless of global seed + rng = random.Random() + self.last_selection = rng.choice(lora_files) + self.current_count = 0 + + selected_lora = self.last_selection + self.current_count += 1 + + elif mode == "Sequential": + if self.current_count >= repeat_count: + self.current_index += 1 + self.current_count = 0 + + selected_lora = lora_files[(self.current_index + manual_index) % count] + self.current_count += 1 + + elif mode == "Manual": + selected_lora = lora_files[manual_index % count] + + # Construct the relative path for ComfyUI Lora loader + # Use forward slashes for consistency + full_lora_name = f"Illustrious/{folder}/{selected_lora}" + + return (full_lora_name, count) + + @classmethod + def IS_CHANGED(s, folder, mode, repeat_count, manual_index): + if mode == "Random" or mode == "Sequential": + return float("nan") + return f"{folder}_{manual_index}" diff --git a/nodes/metadata_saver/__init__.py b/nodes/metadata_saver/__init__.py new file mode 100644 index 0000000..7098f53 --- /dev/null +++ b/nodes/metadata_saver/__init__.py @@ -0,0 +1,9 @@ +from .metadata_saver import AodhMetadataImageSaver + +NODE_CLASS_MAPPINGS = { + "AodhMetadataImageSaver": AodhMetadataImageSaver +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "AodhMetadataImageSaver": "AODH Image Saver (Metadata)" +} diff --git a/nodes/metadata_saver/metadata_saver.py b/nodes/metadata_saver/metadata_saver.py new file mode 100644 index 0000000..62a3f44 --- /dev/null +++ b/nodes/metadata_saver/metadata_saver.py @@ -0,0 +1,210 @@ +import json +import os +import re +import numpy as np +from pathlib import Path +from datetime import datetime +from PIL import Image +from PIL.PngImagePlugin import PngInfo +import folder_paths +import comfy.samplers + +class AodhMetadataImageSaver: + def __init__(self): + self.output_dir = folder_paths.get_output_directory() + self.type = "output" + self.prefix_append = "" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "images": ("IMAGE", ), + "filename_prefix": ("STRING", {"default": "ComfyUI"}), + }, + "optional": { + "save_directory": ("STRING", {"default": "%Y-%m-%d"}), + # Generation parameters + "positive_prompt": ("STRING", {"default": "", "multiline": True}), + "negative_prompt": ("STRING", {"default": "", "multiline": True}), + "checkpoint_name": ("STRING", {"default": ""}), + "vae_name": ("STRING", {"default": ""}), + "clip_skip": ("INT", {"default": -2}), + "lora_name": ("STRING", {"default": ""}), + "lora_strength": ("FLOAT", {"default": 1.0}), + + # Sampling + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + "steps": ("INT", {"default": 20}), + "cfg": ("FLOAT", {"default": 8.0}), + "sampler_name": (comfy.samplers.KSampler.SAMPLERS, ), + "scheduler": (comfy.samplers.KSampler.SCHEDULERS, ), + + # Resolution + "width": ("INT", {"default": 512}), + "height": ("INT", {"default": 512}), + "upscale_factor": ("FLOAT", {"default": 1.0}), + "upscaler_name": ("STRING", {"default": ""}), + "hires_steps": ("INT", {"default": 10}), + "denoise": ("FLOAT", {"default": 0.0}), + + # Character / App Metadata + "character_name": ("STRING", {"default": ""}), + "tags": ("STRING", {"default": ""}), + "rating": (["safe", "questionable", "explicit"], {"default": "safe"}), + + # Extension + "extension": (["png", "jpg", "webp"], {"default": "png"}), + "include_workflow": ("BOOLEAN", {"default": True}), + }, + "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("images",) + FUNCTION = "save_images" + OUTPUT_NODE = True + CATEGORY = "AODH Pack/Image" + + def save_images(self, images, filename_prefix="ComfyUI", save_directory="%Y-%m-%d", + positive_prompt="", negative_prompt="", + checkpoint_name="", vae_name="", clip_skip=-2, lora_name="", lora_strength=1.0, + seed=0, steps=20, cfg=8.0, sampler_name="euler", scheduler="normal", + width=512, height=512, upscale_factor=1.0, upscaler_name="", hires_steps=10, denoise=0.0, + character_name="", tags="", rating="safe", extension="png", include_workflow=True, + prompt=None, extra_pnginfo=None): + + now = datetime.now() + if save_directory: + # Handle date:yyyy-MM-dd format + if "date:" in save_directory: + def replace_date(match): + fmt = match.group(1) + # Convert common JS-like date formats to python strftime + fmt = fmt.replace("yyyy", "%Y").replace("MM", "%m").replace("dd", "%d") + fmt = fmt.replace("HH", "%H").replace("mm", "%M").replace("ss", "%S") + return now.strftime(fmt) + save_directory = re.sub(r"date:([\w\-\:]+)", replace_date, save_directory) + + # Also handle direct strftime patterns if any % is present + if "%" in save_directory: + save_directory = now.strftime(save_directory) + + filename_prefix = os.path.join(save_directory, filename_prefix) + + full_output_folder, filename, counter, subfolder, filename_prefix = \ + folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]) + + results = list() + for image in images: + i = 255. * image.cpu().numpy() + img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) + metadata = PngInfo() + + # 1. Construct A1111 Parameters String + # Format: Prompt + # Negative prompt: ... + # Steps: ..., Sampler: ..., CFG scale: ..., Seed: ..., Size: ...x..., Model: ..., ... + + parameters_text = f"{positive_prompt}\nNegative prompt: {negative_prompt}\n" + parameters_text += f"Steps: {steps}, Sampler: {sampler_name}, Schedule: {scheduler}, CFG scale: {cfg}, Seed: {seed}, " + parameters_text += f"Size: {width}x{height}, Model: {checkpoint_name}" + + if clip_skip != -2: + parameters_text += f", Clip skip: {clip_skip}" + + if upscale_factor > 1.0: + parameters_text += f", Hires upscale: {upscale_factor}, Hires steps: {hires_steps}, Hires upscaler: {upscaler_name}, Denoising strength: {denoise}" + + if lora_name: + display_lora_name = Path(lora_name).name + parameters_text += f", Lora: {display_lora_name}" + + metadata.add_text("parameters", parameters_text) + + # 2. Construct ComfyUI Metadata JSON (following spec) + comfy_meta = { + "_description": "Extended metadata specific to ComfyUI workflow", + "workflow_name": "AODH Generator", # Could be dynamic if we parsed extra_pnginfo + "workflow_version": "1.0.0", + "generation": { + "checkpoint": checkpoint_name, + "vae": vae_name, + "clip_skip": clip_skip, + "lora": [{"name": Path(lora_name).name, "strength_model": lora_strength}] if lora_name else [] + }, + "sampling": { + "sampler": sampler_name, + "scheduler": scheduler, + "steps": steps, + "cfg": cfg, + "seed": seed, + "batch_size": images.shape[0] + }, + "resolution": { + "width": width, + "height": height, + "upscale_factor": upscale_factor, + "upscaler": upscaler_name, + "hires_steps": hires_steps, + "denoise_strength": denoise + }, + "prompt_structure": { + "positive": {"full": positive_prompt}, + "negative": {"full": negative_prompt} + }, + "workflow": {} + } + + # Embed raw workflow if available + if include_workflow: + if prompt is not None: + comfy_meta["workflow"]["execution"] = prompt + if extra_pnginfo is not None and "workflow" in extra_pnginfo: + comfy_meta["workflow"]["nodes"] = extra_pnginfo["workflow"] + + # 3. App Metadata + app_meta = { + "tags": [t.strip() for t in tags.split(",") if t.strip()], + "character": character_name, + "rating": rating + } + + # Add to main JSON + full_metadata = { + "parameters": parameters_text, + "source": "comfyui", + "comfyui": True, + "comfyui_metadata": comfy_meta, + "app_metadata": app_meta + } + + # Embed as "comment" or a specific key. + # The spec says "A ComfyUI node should embed this metadata in the 'parameters' PNG text chunk." + # But usually JSON goes into a separate chunk or we rely on the parser to extract it from 'parameters' if it was formatted there. + # However, standard ComfyUI puts workflow in "workflow" and "prompt" chunks. + # The spec implies a specific JSON structure. We'll add it as a separate text chunk "comfyui_metadata_json" + # OR we can try to append it to parameters, but A1111 parsers might break. + # Let's put the full JSON in a "aodh_metadata" chunk for safety, + # and ALSO try to adhere to standard Comfy behavior by keeping workflow/prompt chunks. + + if include_workflow: + if prompt is not None: + metadata.add_text("prompt", json.dumps(prompt)) + if extra_pnginfo is not None: + for x in extra_pnginfo: + metadata.add_text(x, json.dumps(extra_pnginfo[x])) + + # Add our custom full spec JSON + metadata.add_text("aodh_metadata", json.dumps(full_metadata, indent=2)) + + file = f"{filename}_{counter:05}_.{extension}" + img.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=4) + results.append({ + "filename": file, + "subfolder": subfolder, + "type": self.type + }) + counter += 1 + + return { "ui": { "images": results }, "result": (images,) } diff --git a/nodes/resolution_reader/resolution_reader.py b/nodes/resolution_reader/resolution_reader.py index 128c2b5..7bb76a0 100644 --- a/nodes/resolution_reader/resolution_reader.py +++ b/nodes/resolution_reader/resolution_reader.py @@ -29,17 +29,20 @@ class ResolutionReader: return float("nan") return resolution - RETURN_TYPES = ("INT", "INT", "FLOAT") - RETURN_NAMES = ("width", "height", "upscale") + RETURN_TYPES = ("INT", "INT", "FLOAT", "INT") + RETURN_NAMES = ("width", "height", "upscale", "total_resolutions") FUNCTION = "read_resolution" CATEGORY = "AODH Pack" - _current_index = 0 - _current_count = 0 - _last_selection = None + def __init__(self): + self.current_index = 0 + self.current_count = 0 + self.last_selection = None + self.last_start_resolution = None def read_resolution(self, resolution, selection_mode, repeat_count): + repeat_count = max(1, repeat_count) base_path = os.path.dirname(os.path.realpath(__file__)) file_path = os.path.join(base_path, "resolutions", "resolutions.txt") @@ -49,22 +52,33 @@ class ResolutionReader: lines = [line.strip() for line in f.readlines() if line.strip()] if not lines: - return (0, 0, 0.0) + return (0, 0, 0.0, 0) + + # Reset sequence if resolution changes + if self.last_start_resolution != resolution: + try: + self.current_index = lines.index(resolution) + except ValueError: + self.current_index = 0 + self.current_count = 0 + self.last_start_resolution = resolution if selection_mode == "manual": selected_line = resolution elif selection_mode == "sequential": - if self.__class__._current_count >= repeat_count: - self.__class__._current_index += 1 - self.__class__._current_count = 0 - selected_line = lines[self.__class__._current_index % len(lines)] - self.__class__._current_count += 1 + if self.current_count >= repeat_count: + self.current_index += 1 + self.current_count = 0 + selected_line = lines[self.current_index % len(lines)] + self.current_count += 1 elif selection_mode == "random": - if self.__class__._current_count >= repeat_count or self.__class__._last_selection is None: - self.__class__._last_selection = random.choice(lines) - self.__class__._current_count = 0 - selected_line = self.__class__._last_selection - self.__class__._current_count += 1 + if self.current_count >= repeat_count or self.last_selection is None: + # Use a local Random instance to ensure randomness regardless of global seed + rng = random.Random() + self.last_selection = rng.choice(lines) + self.current_count = 0 + selected_line = self.last_selection + self.current_count += 1 else: selected_line = resolution @@ -74,6 +88,6 @@ class ResolutionReader: width = int(parts[0]) height = int(parts[1]) upscale = float(parts[2]) - return (width, height, upscale) + return (width, height, upscale, len(lines)) except (ValueError, IndexError): - return (0, 0, 0.0) + return (0, 0, 0.0, len(lines)) diff --git a/nodes/resolution_reader/resolutions/resolutions.txt b/nodes/resolution_reader/resolutions/resolutions.txt index f16d7b3..a365fae 100644 --- a/nodes/resolution_reader/resolutions/resolutions.txt +++ b/nodes/resolution_reader/resolutions/resolutions.txt @@ -1,4 +1,8 @@ 1280,720,3.0 720,1280,3.0 1376,576,2.5 +576,1376,2.5 1024,1024,2.0 +1280,800,2.7 +1125,750,2.0 +750,1125,2.0 \ No newline at end of file