Compare commits
10
Commits
copilot/fix-34
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
255a4af7fc | ||
|
|
a253201065 | ||
|
|
e3eb3a2519 | ||
|
|
d0179b3316 | ||
|
|
6ffb6ee696 | ||
|
|
5c32e9463c | ||
|
|
4e917d492e | ||
|
|
84993fad28 | ||
|
|
292be57ab1 | ||
|
|
d479c5a667 |
@@ -1,3 +1,5 @@
|
|||||||
|
[](https://github.com/theUpsider/ComfyUI-Styles_CSV_Loader/actions/workflows/publish.yml)
|
||||||
|
[](https://github.com/theUpsider/ComfyUI-Styles_CSV_Loader/actions/workflows/test.yml)
|
||||||
# Styles CSV Loader Extension for ComfyUI
|
# Styles CSV Loader Extension for ComfyUI
|
||||||
Extension for [ComfyUI](https://github.com/comfyanonymous/ComfyUI) that loads styles from a CSV file.
|
Extension for [ComfyUI](https://github.com/comfyanonymous/ComfyUI) that loads styles from a CSV file.
|
||||||
|
|
||||||
@@ -13,6 +15,15 @@ This extension allows users to load styles from a CSV file (styles.csv), primari
|
|||||||
## Nodes Description
|
## Nodes Description
|
||||||
Each style is represented as a dictionary with the keys being `style_name` and the values being a list containing `positive_prompt` and `negative_prompt`. The prompts are outputs of this Node.
|
Each style is represented as a dictionary with the keys being `style_name` and the values being a list containing `positive_prompt` and `negative_prompt`. The prompts are outputs of this Node.
|
||||||
|
|
||||||
|
### Custom CSV Files
|
||||||
|
The extension now supports loading styles from custom CSV files in addition to the default `styles.csv`. You can specify a custom CSV file path using the optional `csv_file_path` parameter:
|
||||||
|
|
||||||
|
- **Relative paths**: Relative to the ComfyUI root directory (e.g., `"my_styles/portrait_styles.csv"`)
|
||||||
|
- **Absolute paths**: Full file system paths (e.g., `"/path/to/my/custom_styles.csv"`)
|
||||||
|
- **Default behavior**: When no custom path is specified, it uses `styles.csv` from the ComfyUI root directory
|
||||||
|
|
||||||
|
If a selected style is not found in the custom CSV file, the node will fall back to the default styles loaded from `styles.csv`.
|
||||||
|
|
||||||
## CSV Format
|
## CSV Format
|
||||||
The CSV file should have the following format:
|
The CSV file should have the following format:
|
||||||
```csv
|
```csv
|
||||||
@@ -21,6 +32,23 @@ name,prompt,negative_prompt
|
|||||||
"Another Style","more positive text","more negative text"
|
"Another Style","more positive text","more negative text"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
You can organize your styles in multiple CSV files for different purposes:
|
||||||
|
|
||||||
|
**portraits.csv**:
|
||||||
|
```csv
|
||||||
|
name,prompt,negative_prompt
|
||||||
|
"Professional Portrait","portrait photography, professional lighting, sharp focus","ugly, deformed, extra limbs"
|
||||||
|
"Vintage Portrait","vintage portrait, sepia tone, classical","modern, digital, colorful"
|
||||||
|
```
|
||||||
|
|
||||||
|
**landscapes.csv**:
|
||||||
|
```csv
|
||||||
|
name,prompt,negative_prompt
|
||||||
|
"Golden Hour","landscape photography, golden hour, wide angle","people, buildings, urban"
|
||||||
|
"Mountain Vista","mountain landscape, dramatic clouds, epic view","flat, boring, low quality"
|
||||||
|
```
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
### Setting up the development environment
|
### Setting up the development environment
|
||||||
@@ -60,4 +88,4 @@ The project maintains 100% test coverage. Coverage reports are generated during
|
|||||||
## Author
|
## Author
|
||||||
- David Fischer
|
- David Fischer
|
||||||
- GitHub: [theUpsider](https://github.com/theUpsider)
|
- GitHub: [theUpsider](https://github.com/theUpsider)
|
||||||
- Support me on [BuyMeACoffee](https://www.buymeacoffee.com/theupsider)
|
- Support me on [BuyMeACoffee](https://www.buymeacoffee.com/theupsider)
|
||||||
|
|||||||
+8
-1
@@ -1 +1,8 @@
|
|||||||
from styles_csv_loader import StylesCSVLoader
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Add current directory to sys.path to ensure we can import styles_csv_loader
|
||||||
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
if current_dir not in sys.path:
|
||||||
|
sys.path.insert(0, current_dir)
|
||||||
|
from styles_csv_loader import *
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "comfyui-styles_csv_loader"
|
name = "comfyui-styles_csv_loader"
|
||||||
description = "This extension allows users to load styles from a CSV file, primarily for migration purposes from the automatic1111 Stable Diffusion web UI."
|
description = "This extension allows users to load styles from a CSV file, primarily for migration purposes from the automatic1111 Stable Diffusion web UI."
|
||||||
version = "1.1.0"
|
version = "3.0.0"
|
||||||
license = { text = "MIT License" }
|
license = { text = "MIT License" }
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
|||||||
+24
-4
@@ -45,13 +45,15 @@ class StylesCSVLoader:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(cls):
|
def INPUT_TYPES(cls):
|
||||||
# Use os.path.normpath to ensure cross-platform compatibility
|
# Use os.path.normpath to ensure cross-platform compatibility
|
||||||
styles_path = os.path.normpath(os.path.join(folder_paths.base_path, "styles.csv"))
|
default_styles_path = os.path.normpath(os.path.join(folder_paths.base_path, "styles.csv"))
|
||||||
cls.styles_csv = cls.load_styles_csv(styles_path)
|
cls.styles_csv = cls.load_styles_csv(default_styles_path)
|
||||||
return {
|
return {
|
||||||
"required": {
|
"required": {
|
||||||
"styles": (list(cls.styles_csv.keys()),),
|
"styles": (list(cls.styles_csv.keys()),),
|
||||||
},
|
},
|
||||||
|
"optional": {
|
||||||
|
"csv_file_path": ("STRING", {"default": "styles.csv", "multiline": False}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("STRING", "STRING")
|
RETURN_TYPES = ("STRING", "STRING")
|
||||||
@@ -59,7 +61,25 @@ class StylesCSVLoader:
|
|||||||
FUNCTION = "execute"
|
FUNCTION = "execute"
|
||||||
CATEGORY = "loaders"
|
CATEGORY = "loaders"
|
||||||
|
|
||||||
def execute(self, styles):
|
def execute(self, styles, csv_file_path="styles.csv"):
|
||||||
|
# Handle custom CSV file path
|
||||||
|
if csv_file_path != "styles.csv":
|
||||||
|
# Check if path is absolute or relative
|
||||||
|
if os.path.isabs(csv_file_path):
|
||||||
|
custom_styles_path = os.path.normpath(csv_file_path)
|
||||||
|
else:
|
||||||
|
# Relative path from ComfyUI root directory
|
||||||
|
custom_styles_path = os.path.normpath(os.path.join(folder_paths.base_path, csv_file_path))
|
||||||
|
|
||||||
|
# Load styles from custom path
|
||||||
|
custom_styles = self.load_styles_csv(custom_styles_path)
|
||||||
|
if styles in custom_styles:
|
||||||
|
return (custom_styles[styles][0], custom_styles[styles][1])
|
||||||
|
else:
|
||||||
|
# Fall back to default styles if style not found in custom file
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Use default styles
|
||||||
return (self.styles_csv[styles][0], self.styles_csv[styles][1])
|
return (self.styles_csv[styles][0], self.styles_csv[styles][1])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -152,6 +152,82 @@ class TestStylesCSVLoader(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(positive, 'positive prompt here')
|
self.assertEqual(positive, 'positive prompt here')
|
||||||
self.assertEqual(negative, 'negative prompt here')
|
self.assertEqual(negative, 'negative prompt here')
|
||||||
|
|
||||||
|
def test_execute_method_with_custom_csv_path(self):
|
||||||
|
"""Test the execute method with custom CSV file path."""
|
||||||
|
loader = StylesCSVLoader()
|
||||||
|
|
||||||
|
# Mock the default styles_csv attribute
|
||||||
|
loader.styles_csv = {
|
||||||
|
'Default Style': ['default positive', 'default negative']
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test with custom CSV path
|
||||||
|
positive, negative = loader.execute('Cinematic', self.valid_csv_path)
|
||||||
|
|
||||||
|
self.assertEqual(positive, 'cinematic shot, dramatic lighting, film grain')
|
||||||
|
self.assertEqual(negative, 'low quality, blurry, amateur')
|
||||||
|
|
||||||
|
def test_execute_method_with_custom_csv_fallback(self):
|
||||||
|
"""Test that execute method falls back to default when style not found in custom CSV."""
|
||||||
|
loader = StylesCSVLoader()
|
||||||
|
|
||||||
|
# Mock the default styles_csv attribute
|
||||||
|
loader.styles_csv = {
|
||||||
|
'Default Style': ['default positive', 'default negative']
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test with custom CSV path but style that doesn't exist in custom file
|
||||||
|
positive, negative = loader.execute('Default Style', self.valid_csv_path)
|
||||||
|
|
||||||
|
self.assertEqual(positive, 'default positive')
|
||||||
|
self.assertEqual(negative, 'default negative')
|
||||||
|
|
||||||
|
def test_execute_method_with_absolute_path(self):
|
||||||
|
"""Test execute method with absolute path to custom CSV."""
|
||||||
|
loader = StylesCSVLoader()
|
||||||
|
|
||||||
|
# Mock the default styles_csv attribute
|
||||||
|
loader.styles_csv = {
|
||||||
|
'Default Style': ['default positive', 'default negative']
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test with absolute path
|
||||||
|
absolute_path = os.path.abspath(self.valid_csv_path)
|
||||||
|
positive, negative = loader.execute('Cinematic', absolute_path)
|
||||||
|
|
||||||
|
self.assertEqual(positive, 'cinematic shot, dramatic lighting, film grain')
|
||||||
|
self.assertEqual(negative, 'low quality, blurry, amateur')
|
||||||
|
|
||||||
|
@patch('styles_csv_loader.folder_paths')
|
||||||
|
def test_input_types_includes_csv_file_path(self, mock_folder_paths):
|
||||||
|
"""Test that INPUT_TYPES includes the csv_file_path optional parameter."""
|
||||||
|
mock_folder_paths.base_path = self.fixtures_dir
|
||||||
|
|
||||||
|
# Create a temporary styles.csv in the fixtures directory
|
||||||
|
temp_csv_path = os.path.join(self.fixtures_dir, 'styles.csv')
|
||||||
|
with open(temp_csv_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write('name,prompt,negative_prompt\n')
|
||||||
|
f.write('"Test Style","test prompt","test negative"\n')
|
||||||
|
|
||||||
|
try:
|
||||||
|
input_types = StylesCSVLoader.INPUT_TYPES()
|
||||||
|
|
||||||
|
# Check structure includes optional csv_file_path
|
||||||
|
self.assertIn('required', input_types)
|
||||||
|
self.assertIn('optional', input_types)
|
||||||
|
self.assertIn('csv_file_path', input_types['optional'])
|
||||||
|
|
||||||
|
# Check csv_file_path is a STRING type with default
|
||||||
|
csv_param = input_types['optional']['csv_file_path']
|
||||||
|
self.assertEqual(csv_param[0], 'STRING')
|
||||||
|
self.assertIn('default', csv_param[1])
|
||||||
|
self.assertEqual(csv_param[1]['default'], 'styles.csv')
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Clean up
|
||||||
|
if os.path.exists(temp_csv_path):
|
||||||
|
os.remove(temp_csv_path)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
Reference in New Issue
Block a user