Initial implementation of Character Browser & Generator: Gallery, ComfyUI integration, progress tracking, and batch processing.
This commit is contained in:
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
.venv/
|
||||
.env
|
||||
.flaskenv
|
||||
|
||||
# SQLite Database
|
||||
database.db
|
||||
|
||||
# Uploads / Generated Images
|
||||
static/uploads/*
|
||||
!static/uploads/.gitkeep
|
||||
|
||||
# IDEs / Editors
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
67
README.md
Normal file
67
README.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Character Browser & Generator
|
||||
|
||||
A local web-based GUI for managing character profiles (JSON) and generating consistent images using ComfyUI.
|
||||
|
||||
## Features
|
||||
|
||||
- **Character Gallery**: Automatically scans your `characters/` folder and builds a searchable, sortable database.
|
||||
- **Granular Prompt Control**: Every field in your character JSON (Identity, Wardrobe, Styles) has a checkbox. You decide exactly what is sent to the AI.
|
||||
- **ComfyUI Integration**:
|
||||
- **SDXL Optimized**: Designed for high-quality SDXL workflows.
|
||||
- **Localized ADetailer**: Automated Face and Hand detailing with focused prompts (e.g., only eye color and expression are sent to the face detailer).
|
||||
- **LoRA Support**: Automatically detects and applies LoRAs specified in your character sheets.
|
||||
- **Real-time Progress**: Live progress bars and queue status via WebSockets (with a reliable polling fallback).
|
||||
- **Batch Processing**:
|
||||
- **Fill Missing**: Generate covers for every character missing one with a single click.
|
||||
- **Refresh All**: Unassign all current covers and generate a fresh set for the whole collection.
|
||||
- **Advanced Generator**: A dedicated page to mix-and-match characters with different checkpoints (Illustrious/Noob support) and custom prompt additions.
|
||||
- **Smart URLs**: Sanitized, human-readable URLs (slugs) that handle special characters and slashes gracefully.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Python 3.10+**
|
||||
2. **ComfyUI** running locally (default: `http://127.0.0.1:8188`).
|
||||
3. **ComfyUI Custom Nodes**:
|
||||
- [ComfyUI-Impact-Pack](https://github.com/ltdrdata/ComfyUI-Impact-Pack) (Required for FaceDetailer).
|
||||
4. **Models**:
|
||||
- Ensure your Checkpoints are in the folders defined in `app.py`.
|
||||
- Face detection model: `bbox/face_yolov9c.pt` (or update `comfy_workflow.json`).
|
||||
- Hand detection model: `bbox/hand_yolov8s.pt`.
|
||||
|
||||
## Setup & Installation
|
||||
|
||||
1. **Clone the repository** to your local machine.
|
||||
2. **Configure Paths**: Open `app.py` and update the following variables to match your system:
|
||||
```python
|
||||
app.config['COMFYUI_URL'] = 'http://127.0.0.1:8188'
|
||||
app.config['ILLUSTRIOUS_MODELS_DIR'] = '/path/to/your/Illustrious/models'
|
||||
app.config['NOOB_MODELS_DIR'] = '/path/to/your/Noob/models'
|
||||
```
|
||||
3. **Launch**: Simply run the launch script. It will handle the virtual environment and dependencies automatically.
|
||||
```bash
|
||||
chmod +x launch.sh
|
||||
./launch.sh
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Gallery Management
|
||||
- **Rescan**: Use the "Rescan Character Files" button if you've added new JSON files or manually edited them.
|
||||
- **Save Defaults**: On a character page, select your favorite prompt combination and click "Save as Default Selection" to remember it for future quick generations.
|
||||
|
||||
### Generation
|
||||
- **Preview**: Generates an image and shows it to you without replacing your current cover.
|
||||
- **Replace**: Generates an image and sets it as the character's official gallery cover.
|
||||
- **Clean Start**: If you want to wipe the database and all generated images to start fresh:
|
||||
```bash
|
||||
./launch.sh --clean
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
- `/characters`: Your character JSON files.
|
||||
- `/static/uploads`: Generated images (organized by character subfolders).
|
||||
- `/templates`: HTML UI using Bootstrap 5.
|
||||
- `app.py`: Flask backend and prompt-building logic.
|
||||
- `comfy_workflow.json`: The API-format workflow used for generations.
|
||||
- `models.py`: SQLite database schema.
|
||||
544
app.py
Normal file
544
app.py
Normal file
@@ -0,0 +1,544 @@
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import re
|
||||
import requests
|
||||
import random
|
||||
from flask import Flask, render_template, request, redirect, url_for, flash, session
|
||||
from werkzeug.utils import secure_filename
|
||||
from models import db, Character
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
app.config['UPLOAD_FOLDER'] = 'static/uploads'
|
||||
app.config['SECRET_KEY'] = 'dev-key-123'
|
||||
app.config['CHARACTERS_DIR'] = 'characters'
|
||||
app.config['COMFYUI_URL'] = 'http://127.0.0.1:8188'
|
||||
app.config['ILLUSTRIOUS_MODELS_DIR'] = '/mnt/alexander/AITools/Image Models/Stable-diffusion/Illustrious/'
|
||||
app.config['NOOB_MODELS_DIR'] = '/mnt/alexander/AITools/Image Models/Stable-diffusion/Noob/'
|
||||
|
||||
db.init_app(app)
|
||||
|
||||
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
|
||||
|
||||
def get_available_checkpoints():
|
||||
checkpoints = []
|
||||
|
||||
# Scan Illustrious
|
||||
if os.path.exists(app.config['ILLUSTRIOUS_MODELS_DIR']):
|
||||
for f in os.listdir(app.config['ILLUSTRIOUS_MODELS_DIR']):
|
||||
if f.endswith('.safetensors') or f.endswith('.ckpt'):
|
||||
checkpoints.append(f"Illustrious/{f}")
|
||||
|
||||
# Scan Noob
|
||||
if os.path.exists(app.config['NOOB_MODELS_DIR']):
|
||||
for f in os.listdir(app.config['NOOB_MODELS_DIR']):
|
||||
if f.endswith('.safetensors') or f.endswith('.ckpt'):
|
||||
checkpoints.append(f"Noob/{f}")
|
||||
|
||||
return sorted(checkpoints)
|
||||
|
||||
def allowed_file(filename):
|
||||
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||
|
||||
def build_prompt(data, selected_fields=None, default_fields=None):
|
||||
def is_selected(section, key):
|
||||
# Priority:
|
||||
# 1. Manual selection from form (if list is not empty)
|
||||
# 2. Database defaults (if they exist)
|
||||
# 3. Select all (default behavior)
|
||||
if selected_fields:
|
||||
return f"{section}::{key}" in selected_fields
|
||||
if default_fields:
|
||||
return f"{section}::{key}" in default_fields
|
||||
return True
|
||||
|
||||
identity = data.get('identity', {})
|
||||
wardrobe = data.get('wardrobe', {})
|
||||
|
||||
# Pre-calculate Hand/Glove priority
|
||||
hand_val = ""
|
||||
if wardrobe.get('gloves') and is_selected('wardrobe', 'gloves'):
|
||||
hand_val = wardrobe.get('gloves')
|
||||
elif identity.get('hands') and is_selected('identity', 'hands'):
|
||||
hand_val = identity.get('hands')
|
||||
|
||||
# 1. Main Prompt
|
||||
parts = ["(solo:1.2)"]
|
||||
# Use character_id (underscores to spaces) for tags compatibility
|
||||
char_tag = data.get('character_id', '').replace('_', ' ')
|
||||
if char_tag and is_selected('special', 'name'):
|
||||
parts.append(char_tag)
|
||||
|
||||
for key in ['base_specs', 'hair', 'eyes', 'expression', 'distinguishing_marks']:
|
||||
val = identity.get(key)
|
||||
if val and is_selected('identity', key):
|
||||
parts.append(val)
|
||||
|
||||
# Add hand priority value to main prompt
|
||||
if hand_val:
|
||||
parts.append(hand_val)
|
||||
|
||||
for key in ['outer_layer', 'inner_layer', 'lower_body', 'footwear', 'accessories']:
|
||||
val = wardrobe.get(key)
|
||||
if val and is_selected('wardrobe', key):
|
||||
parts.append(val)
|
||||
|
||||
style = data.get('styles', {}).get('aesthetic')
|
||||
if style and is_selected('styles', 'aesthetic'):
|
||||
parts.append(f"{style} style")
|
||||
|
||||
tags = data.get('tags', [])
|
||||
if tags and is_selected('special', 'tags'):
|
||||
parts.extend(tags)
|
||||
|
||||
lora = data.get('lora', {})
|
||||
if lora.get('lora_triggers') and is_selected('lora', 'lora_triggers'):
|
||||
parts.append(lora.get('lora_triggers'))
|
||||
|
||||
# 2. Face Prompt: Tag, Eyes, Expression
|
||||
face_parts = []
|
||||
if char_tag and is_selected('special', 'name'): face_parts.append(char_tag)
|
||||
if identity.get('eyes') and is_selected('identity', 'eyes'): face_parts.append(identity.get('eyes'))
|
||||
if identity.get('expression') and is_selected('identity', 'expression'): face_parts.append(identity.get('expression'))
|
||||
|
||||
# 3. Hand Prompt: Hand value (Gloves or Hands)
|
||||
hand_parts = [hand_val] if hand_val else []
|
||||
|
||||
return {
|
||||
"main": ", ".join(parts),
|
||||
"face": ", ".join(face_parts),
|
||||
"hand": ", ".join(hand_parts)
|
||||
}
|
||||
|
||||
def queue_prompt(prompt_workflow, client_id=None):
|
||||
p = {"prompt": prompt_workflow}
|
||||
if client_id:
|
||||
p["client_id"] = client_id
|
||||
data = json.dumps(p).encode('utf-8')
|
||||
response = requests.post(f"{app.config['COMFYUI_URL']}/prompt", data=data)
|
||||
return response.json()
|
||||
|
||||
def get_history(prompt_id):
|
||||
response = requests.get(f"{app.config['COMFYUI_URL']}/history/{prompt_id}")
|
||||
return response.json()
|
||||
|
||||
def get_image(filename, subfolder, folder_type):
|
||||
data = {"filename": filename, "subfolder": subfolder, "type": folder_type}
|
||||
response = requests.get(f"{app.config['COMFYUI_URL']}/view", params=data)
|
||||
return response.content
|
||||
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
|
||||
def sync_characters():
|
||||
if not os.path.exists(app.config['CHARACTERS_DIR']):
|
||||
return
|
||||
|
||||
current_ids = []
|
||||
|
||||
for filename in os.listdir(app.config['CHARACTERS_DIR']):
|
||||
if filename.endswith('.json'):
|
||||
file_path = os.path.join(app.config['CHARACTERS_DIR'], filename)
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
char_id = data.get('character_id')
|
||||
if not char_id:
|
||||
continue
|
||||
|
||||
current_ids.append(char_id)
|
||||
|
||||
# Generate URL-safe slug: remove special characters from character_id
|
||||
slug = re.sub(r'[^a-zA-Z0-9_]', '', char_id)
|
||||
|
||||
# Check if character already exists
|
||||
character = Character.query.filter_by(character_id=char_id).first()
|
||||
name = data.get('character_name', char_id.replace('_', ' ').title())
|
||||
|
||||
if character:
|
||||
character.data = data
|
||||
character.name = name
|
||||
character.slug = slug
|
||||
|
||||
# Check if cover image still exists
|
||||
if character.image_path:
|
||||
full_img_path = os.path.join(app.config['UPLOAD_FOLDER'], character.image_path)
|
||||
if not os.path.exists(full_img_path):
|
||||
print(f"Image missing for {character.name}, clearing path.")
|
||||
character.image_path = None
|
||||
|
||||
# Explicitly tell SQLAlchemy the JSON field was modified
|
||||
flag_modified(character, "data")
|
||||
else:
|
||||
new_char = Character(
|
||||
character_id=char_id,
|
||||
slug=slug,
|
||||
name=name,
|
||||
data=data
|
||||
)
|
||||
db.session.add(new_char)
|
||||
except Exception as e:
|
||||
print(f"Error importing {filename}: {e}")
|
||||
|
||||
# Remove characters that are no longer in the folder
|
||||
all_characters = Character.query.all()
|
||||
for char in all_characters:
|
||||
if char.character_id not in current_ids:
|
||||
db.session.delete(char)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
characters = Character.query.order_by(Character.name).all()
|
||||
return render_template('index.html', characters=characters)
|
||||
|
||||
@app.route('/rescan', methods=['POST'])
|
||||
def rescan():
|
||||
sync_characters()
|
||||
flash('Database synced with character files.')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@app.route('/generator', methods=['GET', 'POST'])
|
||||
def generator():
|
||||
characters = Character.query.order_by(Character.name).all()
|
||||
checkpoints = get_available_checkpoints()
|
||||
|
||||
if not checkpoints:
|
||||
checkpoints = ["Noob/oneObsession_v19Atypical.safetensors"]
|
||||
|
||||
if request.method == 'POST':
|
||||
char_slug = request.form.get('character')
|
||||
checkpoint = request.form.get('checkpoint')
|
||||
custom_positive = request.form.get('positive_prompt', '')
|
||||
custom_negative = request.form.get('negative_prompt', '')
|
||||
|
||||
character = Character.query.filter_by(slug=char_slug).first_or_404()
|
||||
|
||||
try:
|
||||
with open('comfy_workflow.json', 'r') as f:
|
||||
workflow = json.load(f)
|
||||
|
||||
# Build base prompts from character defaults
|
||||
prompts = build_prompt(character.data, default_fields=character.default_fields)
|
||||
|
||||
# Append custom additions to the "main" prompt
|
||||
if custom_positive:
|
||||
prompts["main"] = f"{prompts['main']}, {custom_positive}"
|
||||
|
||||
# Prepare workflow with custom checkpoint and negative prompt
|
||||
workflow = _prepare_workflow(workflow, character, prompts, checkpoint, custom_negative)
|
||||
|
||||
print(f"Queueing generator prompt for {character.character_id}")
|
||||
prompt_response = queue_prompt(workflow)
|
||||
|
||||
if 'prompt_id' not in prompt_response:
|
||||
raise Exception(f"ComfyUI failed: {prompt_response.get('error', 'Unknown error')}")
|
||||
|
||||
prompt_id = prompt_response['prompt_id']
|
||||
flash("Generation started...")
|
||||
|
||||
max_retries = 120
|
||||
while max_retries > 0:
|
||||
history = get_history(prompt_id)
|
||||
if prompt_id in history:
|
||||
outputs = history[prompt_id]['outputs']
|
||||
for node_id in outputs:
|
||||
if 'images' in outputs[node_id]:
|
||||
image_info = outputs[node_id]['images'][0]
|
||||
image_data = get_image(image_info['filename'], image_info['subfolder'], image_info['type'])
|
||||
|
||||
char_folder = os.path.join(app.config['UPLOAD_FOLDER'], character.slug)
|
||||
os.makedirs(char_folder, exist_ok=True)
|
||||
filename = f"gen_{int(time.time())}.png"
|
||||
file_path = os.path.join(char_folder, filename)
|
||||
with open(file_path, 'wb') as f:
|
||||
f.write(image_data)
|
||||
|
||||
relative_path = f"{character.slug}/{filename}"
|
||||
return render_template('generator.html', characters=characters, checkpoints=checkpoints,
|
||||
generated_image=relative_path, selected_char=char_slug, selected_ckpt=checkpoint)
|
||||
time.sleep(2)
|
||||
max_retries -= 1
|
||||
flash("Generation timed out.")
|
||||
except Exception as e:
|
||||
print(f"Generator error: {e}")
|
||||
flash(f"Error: {str(e)}")
|
||||
|
||||
return render_template('generator.html', characters=characters, checkpoints=checkpoints)
|
||||
|
||||
@app.route('/character/<path:slug>')
|
||||
def detail(slug):
|
||||
character = Character.query.filter_by(slug=slug).first_or_404()
|
||||
|
||||
# Load state from session
|
||||
preferences = session.get(f'prefs_{slug}')
|
||||
preview_image = session.get(f'preview_{slug}')
|
||||
|
||||
return render_template('detail.html', character=character, preferences=preferences, preview_image=preview_image)
|
||||
|
||||
@app.route('/character/<path:slug>/upload', methods=['POST'])
|
||||
def upload_image(slug):
|
||||
character = Character.query.filter_by(slug=slug).first_or_404()
|
||||
|
||||
if 'image' not in request.files:
|
||||
flash('No file part')
|
||||
return redirect(request.url)
|
||||
|
||||
file = request.files['image']
|
||||
if file.filename == '':
|
||||
flash('No selected file')
|
||||
return redirect(request.url)
|
||||
|
||||
if file and allowed_file(file.filename):
|
||||
# Create character subfolder
|
||||
char_folder = os.path.join(app.config['UPLOAD_FOLDER'], slug)
|
||||
os.makedirs(char_folder, exist_ok=True)
|
||||
|
||||
filename = secure_filename(file.filename)
|
||||
file_path = os.path.join(char_folder, filename)
|
||||
file.save(file_path)
|
||||
|
||||
# Store relative path in DB
|
||||
character.image_path = f"{slug}/{filename}"
|
||||
db.session.commit()
|
||||
flash('Image uploaded successfully!')
|
||||
|
||||
return redirect(url_for('detail', slug=slug))
|
||||
|
||||
@app.route('/character/<path:slug>/finalize_generation/<prompt_id>', methods=['POST'])
|
||||
def finalize_generation(slug, prompt_id):
|
||||
character = Character.query.filter_by(slug=slug).first_or_404()
|
||||
action = request.form.get('action', 'preview')
|
||||
|
||||
try:
|
||||
history = get_history(prompt_id)
|
||||
if prompt_id not in history:
|
||||
return {'error': 'History not found'}, 404
|
||||
|
||||
outputs = history[prompt_id]['outputs']
|
||||
for node_id in outputs:
|
||||
if 'images' in outputs[node_id]:
|
||||
image_info = outputs[node_id]['images'][0]
|
||||
image_data = get_image(image_info['filename'], image_info['subfolder'], image_info['type'])
|
||||
|
||||
# Create character subfolder
|
||||
char_folder = os.path.join(app.config['UPLOAD_FOLDER'], slug)
|
||||
os.makedirs(char_folder, exist_ok=True)
|
||||
|
||||
filename = f"gen_{int(time.time())}.png"
|
||||
file_path = os.path.join(char_folder, filename)
|
||||
with open(file_path, 'wb') as f:
|
||||
f.write(image_data)
|
||||
|
||||
print(f"Image saved to: {os.path.abspath(file_path)}")
|
||||
|
||||
# Handle actions
|
||||
relative_path = f"{slug}/{filename}"
|
||||
|
||||
if action == 'replace':
|
||||
character.image_path = relative_path
|
||||
db.session.commit()
|
||||
flash('Cover image updated!')
|
||||
else:
|
||||
# Preview mode
|
||||
session[f'preview_{slug}'] = relative_path
|
||||
|
||||
return {'success': True, 'image_url': url_for('static', filename=f'uploads/{relative_path}')}
|
||||
|
||||
return {'error': 'No image found in output'}, 404
|
||||
except Exception as e:
|
||||
print(f"Finalize error: {e}")
|
||||
return {'error': str(e)}, 500
|
||||
|
||||
def _prepare_workflow(workflow, character, prompts, checkpoint=None, custom_negative=None):
|
||||
# 1. Update prompts using replacement to preserve embeddings
|
||||
workflow["6"]["inputs"]["text"] = workflow["6"]["inputs"]["text"].replace("{{POSITIVE_PROMPT}}", prompts["main"])
|
||||
|
||||
if custom_negative:
|
||||
workflow["7"]["inputs"]["text"] = f"{workflow['7']['inputs']['text']}, {custom_negative}"
|
||||
|
||||
if "14" in workflow:
|
||||
workflow["14"]["inputs"]["text"] = workflow["14"]["inputs"]["text"].replace("{{FACE_PROMPT}}", prompts["face"])
|
||||
if "15" in workflow:
|
||||
workflow["15"]["inputs"]["text"] = workflow["15"]["inputs"]["text"].replace("{{HAND_PROMPT}}", prompts["hand"])
|
||||
|
||||
print("--- DEBUG: COMFYUI PROMPTS ---")
|
||||
print(f"Main Positive (6): {workflow['6']['inputs']['text']}")
|
||||
print(f"Main Negative (7): {workflow['7']['inputs']['text']}")
|
||||
if "14" in workflow:
|
||||
print(f"Face Detailer (14): {workflow['14']['inputs']['text']}")
|
||||
if "15" in workflow:
|
||||
print(f"Hand Detailer (15): {workflow['15']['inputs']['text']}")
|
||||
print("-------------------------------")
|
||||
|
||||
# 2. Update Checkpoint
|
||||
if checkpoint:
|
||||
workflow["4"]["inputs"]["ckpt_name"] = checkpoint
|
||||
|
||||
# 3. Handle LoRA
|
||||
lora_data = character.data.get('lora', {})
|
||||
lora_name = lora_data.get('lora_name')
|
||||
|
||||
model_source = ["4", 0]
|
||||
clip_source = ["4", 1]
|
||||
|
||||
if lora_name and "16" in workflow:
|
||||
workflow["16"]["inputs"]["lora_name"] = lora_name
|
||||
workflow["16"]["inputs"]["strength_model"] = lora_data.get('lora_weight', 1.0)
|
||||
workflow["16"]["inputs"]["strength_clip"] = lora_data.get('lora_weight', 1.0)
|
||||
model_source = ["16", 0]
|
||||
clip_source = ["16", 1]
|
||||
|
||||
# Apply connections to all model/clip consumers
|
||||
workflow["3"]["inputs"]["model"] = model_source
|
||||
workflow["11"]["inputs"]["model"] = model_source
|
||||
workflow["13"]["inputs"]["model"] = model_source
|
||||
|
||||
workflow["6"]["inputs"]["clip"] = clip_source
|
||||
workflow["7"]["inputs"]["clip"] = clip_source
|
||||
workflow["11"]["inputs"]["clip"] = clip_source
|
||||
workflow["13"]["inputs"]["clip"] = clip_source
|
||||
workflow["14"]["inputs"]["clip"] = clip_source
|
||||
workflow["15"]["inputs"]["clip"] = clip_source
|
||||
|
||||
# 4. Randomize seeds
|
||||
gen_seed = random.randint(1, 10**15)
|
||||
workflow["3"]["inputs"]["seed"] = gen_seed
|
||||
if "11" in workflow: workflow["11"]["inputs"]["seed"] = gen_seed
|
||||
if "13" in workflow: workflow["13"]["inputs"]["seed"] = gen_seed
|
||||
|
||||
return workflow
|
||||
|
||||
def _queue_generation(character, action='preview', selected_fields=None, client_id=None):
|
||||
# 1. Load workflow
|
||||
with open('comfy_workflow.json', 'r') as f:
|
||||
workflow = json.load(f)
|
||||
|
||||
# 2. Build prompts
|
||||
prompts = build_prompt(character.data, selected_fields, character.default_fields)
|
||||
|
||||
# 3. Prepare workflow
|
||||
workflow = _prepare_workflow(workflow, character, prompts)
|
||||
|
||||
return queue_prompt(workflow, client_id=client_id)
|
||||
@app.route('/get_missing_characters')
|
||||
def get_missing_characters():
|
||||
missing = Character.query.filter((Character.image_path == None) | (Character.image_path == '')).all()
|
||||
return {'missing': [{'slug': c.slug, 'name': c.name} for c in missing]}
|
||||
|
||||
@app.route('/clear_all_covers', methods=['POST'])
|
||||
def clear_all_covers():
|
||||
characters = Character.query.all()
|
||||
for char in characters:
|
||||
char.image_path = None
|
||||
db.session.commit()
|
||||
return {'success': True}
|
||||
|
||||
@app.route('/generate_missing', methods=['POST'])
|
||||
def generate_missing():
|
||||
missing = Character.query.filter((Character.image_path == None) | (Character.image_path == '')).all()
|
||||
if not missing:
|
||||
flash("No characters missing cover images.")
|
||||
return redirect(url_for('index'))
|
||||
|
||||
success_count = 0
|
||||
for character in missing:
|
||||
try:
|
||||
print(f"Batch generating for: {character.name}")
|
||||
prompt_response = _queue_generation(character, action='replace')
|
||||
prompt_id = prompt_response['prompt_id']
|
||||
|
||||
# Simple synchronous wait for each
|
||||
max_retries = 120
|
||||
while max_retries > 0:
|
||||
history = get_history(prompt_id)
|
||||
if prompt_id in history:
|
||||
outputs = history[prompt_id]['outputs']
|
||||
for node_id in outputs:
|
||||
if 'images' in outputs[node_id]:
|
||||
image_info = outputs[node_id]['images'][0]
|
||||
image_data = get_image(image_info['filename'], image_info['subfolder'], image_info['type'])
|
||||
|
||||
char_folder = os.path.join(app.config['UPLOAD_FOLDER'], character.slug)
|
||||
os.makedirs(char_folder, exist_ok=True)
|
||||
filename = f"gen_{int(time.time())}.png"
|
||||
file_path = os.path.join(char_folder, filename)
|
||||
with open(file_path, 'wb') as f:
|
||||
f.write(image_data)
|
||||
|
||||
character.image_path = f"{character.slug}/{filename}"
|
||||
db.session.commit()
|
||||
success_count += 1
|
||||
break
|
||||
break
|
||||
time.sleep(2)
|
||||
max_retries -= 1
|
||||
except Exception as e:
|
||||
print(f"Error generating for {character.name}: {e}")
|
||||
|
||||
flash(f"Batch generation complete. Generated {success_count} images.")
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@app.route('/check_status/<prompt_id>')
|
||||
def check_status(prompt_id):
|
||||
try:
|
||||
history = get_history(prompt_id)
|
||||
if prompt_id in history:
|
||||
return {'status': 'finished'}
|
||||
return {'status': 'pending'}
|
||||
except Exception:
|
||||
return {'status': 'error'}, 500
|
||||
|
||||
@app.route('/character/<path:slug>/generate', methods=['POST'])
|
||||
def generate_image(slug):
|
||||
character = Character.query.filter_by(slug=slug).first_or_404()
|
||||
|
||||
try:
|
||||
# Get action type
|
||||
action = request.form.get('action', 'preview')
|
||||
client_id = request.form.get('client_id')
|
||||
|
||||
# Get selected fields
|
||||
selected_fields = request.form.getlist('include_field')
|
||||
|
||||
# Save preferences
|
||||
session[f'prefs_{slug}'] = selected_fields
|
||||
|
||||
# Queue generation using helper
|
||||
prompt_response = _queue_generation(character, action, selected_fields, client_id=client_id)
|
||||
|
||||
if 'prompt_id' not in prompt_response:
|
||||
raise Exception(f"ComfyUI failed: {prompt_response.get('error', 'Unknown error')}")
|
||||
|
||||
prompt_id = prompt_response['prompt_id']
|
||||
|
||||
# Return JSON if AJAX request
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return {'status': 'queued', 'prompt_id': prompt_id}
|
||||
|
||||
return redirect(url_for('detail', slug=slug))
|
||||
|
||||
except Exception as e:
|
||||
print(f"Generation error: {e}")
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return {'error': str(e)}, 500
|
||||
flash(f"Error during generation: {str(e)}")
|
||||
return redirect(url_for('detail', slug=slug))
|
||||
|
||||
@app.route('/character/<path:slug>/save_defaults', methods=['POST'])
|
||||
def save_defaults(slug):
|
||||
character = Character.query.filter_by(slug=slug).first_or_404()
|
||||
selected_fields = request.form.getlist('include_field')
|
||||
character.default_fields = selected_fields
|
||||
db.session.commit()
|
||||
flash('Default prompt selection saved for this character!')
|
||||
return redirect(url_for('detail', slug=slug))
|
||||
|
||||
if __name__ == '__main__':
|
||||
with app.app_context():
|
||||
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
||||
db.create_all()
|
||||
sync_characters()
|
||||
app.run(debug=True, port=5000)
|
||||
38
characters/aerith_gainsborough.json
Normal file
38
characters/aerith_gainsborough.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"character_id": "aerith_gainsborough",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "long brown hair, braided, pink ribbon",
|
||||
"eyes": "green eyes",
|
||||
"expression": "cheerful expression",
|
||||
"hands": "pink nails",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "pink dress, red bolero jacket",
|
||||
"lower_body": "long skirt",
|
||||
"footwear": "brown boots",
|
||||
"gloves": "",
|
||||
"accessories": "gold bracelets, flower basket"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "floral, gentle, final fantasy style",
|
||||
"primary_color": "pink",
|
||||
"secondary_color": "red",
|
||||
"tertiary_color": "brown"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Final Fantasy VII"
|
||||
]
|
||||
}
|
||||
39
characters/android_18.json
Normal file
39
characters/android_18.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "android_18",
|
||||
"character_name": "Android 18",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "shoulder-length blonde hair, tucked behind one ear",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "cool, indifferent expression",
|
||||
"hands": "blue nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "gold hoop earrings"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "black short-sleeved shirt",
|
||||
"outer_layer": "blue denim vest, 'RR' text on back",
|
||||
"lower_body": "blue denim skirt, black leggings",
|
||||
"footwear": "brown boots",
|
||||
"gloves": "",
|
||||
"accessories": ""
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "90s casual, anime, dragon ball style",
|
||||
"primary_color": "blue",
|
||||
"secondary_color": "black",
|
||||
"tertiary_color": "white"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Dragon Ball Z"
|
||||
]
|
||||
}
|
||||
39
characters/anya_forger.json
Normal file
39
characters/anya_forger.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "anya_(spy_x_family)",
|
||||
"character_name": "Anya Forger",
|
||||
"identity": {
|
||||
"base_specs": "1girl, small build, loli, fair skin",
|
||||
"hair": "short pink hair, two small horns (hair ornaments)",
|
||||
"eyes": "green eyes",
|
||||
"expression": "smirk",
|
||||
"hands": "pink nails",
|
||||
"arms": "",
|
||||
"torso": "flat chest",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "black Eden Academy uniform, gold trim",
|
||||
"lower_body": "uniform skirt",
|
||||
"footwear": "black shoes, white socks",
|
||||
"gloves": "",
|
||||
"accessories": "black and gold hair cones"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "cute, academic, spy x family style",
|
||||
"primary_color": "pink",
|
||||
"secondary_color": "black",
|
||||
"tertiary_color": "gold"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Spy x Family"
|
||||
]
|
||||
}
|
||||
39
characters/biwa_hayahide.json
Normal file
39
characters/biwa_hayahide.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "biwa_hayahide_(Umamusume)",
|
||||
"character_name": "Biwa Hayahide",
|
||||
"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": ""
|
||||
},
|
||||
"tags": [
|
||||
"Umamusume"
|
||||
]
|
||||
}
|
||||
39
characters/bulma.json
Normal file
39
characters/bulma.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "bulma",
|
||||
"character_name": "Bulma Briefs",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "turquoise hair, ponytail",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "energetic smile",
|
||||
"hands": "turquoise nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "black playboy bunny",
|
||||
"lower_body": "pantyhose",
|
||||
"footwear": "red high heels",
|
||||
"gloves": "detatched cuffs",
|
||||
"accessories": "red hair ribbon, dragon radar"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "retro-futuristic, anime, dragon ball style",
|
||||
"primary_color": "pink",
|
||||
"secondary_color": "turquoise",
|
||||
"tertiary_color": "purple"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Dragon Ball"
|
||||
]
|
||||
}
|
||||
39
characters/camilla.json
Normal file
39
characters/camilla.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "camilla_(fire_emblem)",
|
||||
"character_name": "Camilla Nohr",
|
||||
"identity": {
|
||||
"base_specs": "1girl, curvaceous build, fair skin",
|
||||
"hair": "long wavy lavender hair, hair covering one eye",
|
||||
"eyes": "purple eyes",
|
||||
"expression": "seductive smile",
|
||||
"hands": "purple nails",
|
||||
"arms": "",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "black headband with horns"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "black armor, cleavage",
|
||||
"lower_body": "black leggings, armored plates",
|
||||
"footwear": "black armored boots",
|
||||
"gloves": "",
|
||||
"accessories": "purple cape, large axe"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "dark fantasy, gothic, fire emblem style",
|
||||
"primary_color": "black",
|
||||
"secondary_color": "gold",
|
||||
"tertiary_color": "purple"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Fire Emblem"
|
||||
]
|
||||
}
|
||||
39
characters/cammy.json
Normal file
39
characters/cammy.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "cammy",
|
||||
"character_name": "Cammy White",
|
||||
"identity": {
|
||||
"base_specs": "1girl, muscular build, fair skin",
|
||||
"hair": "long blonde hair, twin braids",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "serious look",
|
||||
"hands": "green nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "scar on left cheek, green camouflage paint on legs"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "green high-leg leotard",
|
||||
"lower_body": "bare legs",
|
||||
"footwear": "black combat boots, green socks",
|
||||
"gloves": "red gauntlets",
|
||||
"accessories": "red beret"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "military, athletic, street fighter style",
|
||||
"primary_color": "green",
|
||||
"secondary_color": "red",
|
||||
"tertiary_color": "black"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Street Fighter"
|
||||
]
|
||||
}
|
||||
39
characters/chun_li.json
Normal file
39
characters/chun_li.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "chun_li",
|
||||
"character_name": "Chun-Li",
|
||||
"identity": {
|
||||
"base_specs": "1girl, muscular build, fair skin, asian",
|
||||
"hair": "black hair, hair buns",
|
||||
"eyes": "brown eyes",
|
||||
"expression": "determined smile",
|
||||
"hands": "blue nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "thick thighs",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "blue qipao, gold embroidery, white accents",
|
||||
"lower_body": "brown tights",
|
||||
"footwear": "white combat boots",
|
||||
"gloves": "",
|
||||
"accessories": "white hair ribbons, spiked bracelets"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "chinese style",
|
||||
"primary_color": "blue",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "gold"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Street Fighter"
|
||||
]
|
||||
}
|
||||
39
characters/ciri.json
Normal file
39
characters/ciri.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "ciri",
|
||||
"character_name": "Ciri",
|
||||
"identity": {
|
||||
"base_specs": "1girl, athletic build",
|
||||
"hair": "ashen grey hair, messy bun",
|
||||
"eyes": "emerald green eyes, mascara",
|
||||
"expression": "determined look",
|
||||
"hands": "green nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "scar over eye"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "white blouse",
|
||||
"outer_layer": "",
|
||||
"lower_body": "brown leather trousers",
|
||||
"footwear": "brown leather boots",
|
||||
"gloves": "brown leather gloves",
|
||||
"accessories": "silver sword on back, witcher medallion"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "gritty, fantasy, witcher style",
|
||||
"primary_color": "white",
|
||||
"secondary_color": "brown",
|
||||
"tertiary_color": "silver"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"The Witcher 3"
|
||||
]
|
||||
}
|
||||
39
characters/delinquent_mother_flim13.json
Normal file
39
characters/delinquent_mother_flim13.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "delinquent_mother_flim13",
|
||||
"character_name": "Delinquent Mother",
|
||||
"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": ""
|
||||
},
|
||||
"tags": [
|
||||
"Original","flim13"
|
||||
]
|
||||
}
|
||||
39
characters/gold_city.json
Normal file
39
characters/gold_city.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "gold_city_(Umamusume)",
|
||||
"character_name": "Gold City",
|
||||
"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": ""
|
||||
},
|
||||
"tags": [
|
||||
"Umamusume"
|
||||
]
|
||||
}
|
||||
39
characters/gold_ship.json
Normal file
39
characters/gold_ship.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "gold_ship_(Umamusume)",
|
||||
"character_name": "Gold Ship",
|
||||
"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": ""
|
||||
},
|
||||
"tags": [
|
||||
"Umamusume"
|
||||
]
|
||||
}
|
||||
39
characters/hatsune_miku.json
Normal file
39
characters/hatsune_miku.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "hatsune_miku",
|
||||
"character_name": "Hatsune Miku",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "long turquoise hair, twin tails, floor-length",
|
||||
"eyes": "turquoise eyes",
|
||||
"expression": "cheerful smile",
|
||||
"hands": "turquoise nails",
|
||||
"arms": "01 tattoo on left shoulder",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "grey sleeveless shirt, turquoise tie",
|
||||
"lower_body": "grey miniskirt, turquoise trim",
|
||||
"footwear": "black thigh-high boots, turquoise trim",
|
||||
"gloves": "black arm warmers, turquoise trim",
|
||||
"accessories": "hair ornament, headset"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "vocaloid, futuristic, anime style",
|
||||
"primary_color": "teal",
|
||||
"secondary_color": "grey",
|
||||
"tertiary_color": "black"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Vocaloid"
|
||||
]
|
||||
}
|
||||
39
characters/jessica_rabbit.json
Normal file
39
characters/jessica_rabbit.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "jessica_rabbit",
|
||||
"character_name": "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": ""
|
||||
},
|
||||
"tags": [
|
||||
"Who Framed Roger Rabbit"
|
||||
]
|
||||
}
|
||||
39
characters/jessie.json
Normal file
39
characters/jessie.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "jessie_(pokemon)",
|
||||
"character_name": "Jessie",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "long magenta hair, curved back",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "arrogant smirk",
|
||||
"hands": "white nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "green earrings"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "black crop top",
|
||||
"outer_layer": "white Team Rocket uniform jacket, bare stomach, red R logo",
|
||||
"lower_body": "white miniskirt",
|
||||
"footwear": "black thigh-high boots",
|
||||
"gloves": "black elbow gloves",
|
||||
"accessories": "green earrings"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "villainous, anime, pokemon style",
|
||||
"primary_color": "white",
|
||||
"secondary_color": "magenta",
|
||||
"tertiary_color": "black"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Pokemon"
|
||||
]
|
||||
}
|
||||
39
characters/jinx.json
Normal file
39
characters/jinx.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "jinx_(league_of_legends)",
|
||||
"character_name": "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, 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": ""
|
||||
},
|
||||
"tags": [
|
||||
"League of Legends"
|
||||
]
|
||||
}
|
||||
39
characters/kagamine_rin.json
Normal file
39
characters/kagamine_rin.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "kagamine_rin",
|
||||
"character_name": "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": ""
|
||||
},
|
||||
"tags": [
|
||||
"Vocaloid"
|
||||
]
|
||||
}
|
||||
39
characters/kagari_atsuko.json
Normal file
39
characters/kagari_atsuko.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "kagari_atsuko",
|
||||
"character_name": "Kagari Atsuko",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "long brown hair, half-ponytail, bangs",
|
||||
"eyes": "red eyes",
|
||||
"expression": "determined smile",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "white shirt",
|
||||
"outer_layer": "dark blue witch robes",
|
||||
"lower_body": "dark blue skirt",
|
||||
"footwear": "brown boots, white socks",
|
||||
"gloves": "",
|
||||
"accessories": "pointed witch hat, brown belt, magic wand"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "fantasy, magical girl, little witch academia style",
|
||||
"primary_color": "dark blue",
|
||||
"secondary_color": "brown",
|
||||
"tertiary_color": "red"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Little Witch Academia"
|
||||
]
|
||||
}
|
||||
39
characters/kda_all_out_ahri.json
Normal file
39
characters/kda_all_out_ahri.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "k/da_all_out_ahri",
|
||||
"character_name": "Ahri",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, fair skin, fox ears",
|
||||
"hair": "long blonde hair, flowing",
|
||||
"eyes": "yellow eyes",
|
||||
"expression": "charming smile",
|
||||
"hands": "silver nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "whisker markings on cheeks, crystal tails"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "silver crop top",
|
||||
"outer_layer": "white and silver jacket",
|
||||
"lower_body": "black leather shorts",
|
||||
"footwear": "black thigh-high boots",
|
||||
"gloves": "",
|
||||
"accessories": "crystal heart, silver jewelry"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "pop star, mystical, k/da style",
|
||||
"primary_color": "silver",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "blue"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/KDA AhriIlluLoRA.safetensors",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"League of Legends", "K/DA", "KDA", "K-Pop"
|
||||
]
|
||||
}
|
||||
39
characters/kda_all_out_akali.json
Normal file
39
characters/kda_all_out_akali.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "k/da_all_out_akali",
|
||||
"character_name": "Akali",
|
||||
"identity": {
|
||||
"base_specs": "1girl, athletic build, fair skin",
|
||||
"hair": "long dark blue hair, blonde streaks, high ponytail",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "cool, rebellious look",
|
||||
"hands": "blue nails",
|
||||
"arms": "tattoos on arms",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "black crop top",
|
||||
"outer_layer": "blue and silver motorcycle jacket",
|
||||
"lower_body": "black leather pants",
|
||||
"footwear": "blue sneakers",
|
||||
"gloves": "black fingerless gloves",
|
||||
"accessories": "kama and kunai"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "pop star, street, k/da style",
|
||||
"primary_color": "blue",
|
||||
"secondary_color": "purple",
|
||||
"tertiary_color": "silver"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/KDAAkaliIlluLoRA.safetensors",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"League of Legends", "K/DA", "KDA", "K-Pop"
|
||||
]
|
||||
}
|
||||
39
characters/kda_all_out_evelynn.json
Normal file
39
characters/kda_all_out_evelynn.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "k/da_all_out_evelynn",
|
||||
"character_name": "Evelynn",
|
||||
"identity": {
|
||||
"base_specs": "1girl, curvaceous build, fair skin",
|
||||
"hair": "light blue hair,",
|
||||
"eyes": "yellow glowing eyes, slit pupils",
|
||||
"expression": "seductive, confident look",
|
||||
"hands": "metal claws",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "two long lashers (shadow tendrils)"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "black leather bra",
|
||||
"outer_layer": "iridescent blue jacket, fur collar",
|
||||
"lower_body": "black leather skirt",
|
||||
"footwear": "black high-heeled boots",
|
||||
"gloves": "",
|
||||
"accessories": "diamond earrings"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "pop star, glamorous, k/da style",
|
||||
"primary_color": "blue",
|
||||
"secondary_color": "purple",
|
||||
"tertiary_color": "silver"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/KDA EvelynnIlluLoRA.safetensors",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"League of Legends", "K/DA", "KDA", "K-Pop"
|
||||
]
|
||||
}
|
||||
39
characters/kda_all_out_kaisa.json
Normal file
39
characters/kda_all_out_kaisa.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "k/da_all_out_kai'sa",
|
||||
"character_name": "Kai'Sa",
|
||||
"identity": {
|
||||
"base_specs": "1girl, athletic build, fair skin",
|
||||
"hair": "long hair, purple hair, hair ornament, ponytail, green highlights",
|
||||
"eyes": "purple eyes",
|
||||
"expression": "focused expression",
|
||||
"hands": "silver nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "silver bodysuit",
|
||||
"outer_layer": "white and silver jacket",
|
||||
"lower_body": "silver leggings",
|
||||
"footwear": "silver high-heeled boots",
|
||||
"gloves": "",
|
||||
"accessories": "crystal shoulder pods"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "pop star, futuristic, k/da style",
|
||||
"primary_color": "silver",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "purple"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/KDA KaisaIlluLoRA.safetensors",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"League of Legends", "K/DA", "KDA", "K-Pop"
|
||||
]
|
||||
}
|
||||
39
characters/komi_shouko.json
Normal file
39
characters/komi_shouko.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "komi_shouko",
|
||||
"character_name": "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"
|
||||
},
|
||||
"tags": [
|
||||
"Komi Can't Communicate"
|
||||
]
|
||||
}
|
||||
39
characters/lara_croft_classic.json
Normal file
39
characters/lara_croft_classic.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "lara_croft_classic",
|
||||
"character_name": "Lara Croft",
|
||||
"identity": {
|
||||
"base_specs": "1girl, athletic build,",
|
||||
"hair": "long brown hair, single braid",
|
||||
"eyes": "brown eyes",
|
||||
"expression": "light smile, raised eyebrow",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "teal tank top,",
|
||||
"lower_body": "brown shorts",
|
||||
"footwear": "brown combat boots, red laces",
|
||||
"gloves": "black fingerless gloves",
|
||||
"accessories": "dual thigh pistol holsters, brown leatherbackpack, red circular sunglasses"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "adventure, retro, 90s style",
|
||||
"primary_color": "teal",
|
||||
"secondary_color": "brown",
|
||||
"tertiary_color": "black"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/LaraCroft_ClassicV2_Illu_Dwnsty.safetensors",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Tomb Raider"
|
||||
]
|
||||
}
|
||||
39
characters/lisa_minci.json
Normal file
39
characters/lisa_minci.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "lisa_(genshin_impact)",
|
||||
"character_name": "Lisa Minci",
|
||||
"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": ""
|
||||
},
|
||||
"tags": [
|
||||
"Genshin Impact"
|
||||
]
|
||||
}
|
||||
39
characters/lulu.json
Normal file
39
characters/lulu.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "lulu (ff10)",
|
||||
"character_name": "Lulu",
|
||||
"identity": {
|
||||
"base_specs": "1girl, curvaceous build, fair skin",
|
||||
"hair": "long black hair, complex braids, hairpins",
|
||||
"eyes": "red eyes",
|
||||
"expression": "thinking, raised eyebrow",
|
||||
"hands": "black nails",
|
||||
"arms": "",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "dark purple lipstick"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "black corset",
|
||||
"outer_layer": "black fur-trimmed dress, many belts on front",
|
||||
"lower_body": "long skirt made of belts",
|
||||
"footwear": "black boots",
|
||||
"gloves": "",
|
||||
"accessories": "moogle doll, silver jewelry"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "gothic, ornate, final fantasy x style",
|
||||
"primary_color": "black",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "purple"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/Lulu DG illuLoRA_1337272.safetensors",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Final Fantasy X"
|
||||
]
|
||||
}
|
||||
39
characters/majin_android_21.json
Normal file
39
characters/majin_android_21.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "majin_android_21",
|
||||
"character_name": "Majin Android 21",
|
||||
"identity": {
|
||||
"base_specs": "1girl, curvaceous build, pink skin",
|
||||
"hair": "long voluminous white hair",
|
||||
"eyes": "red eyes, black sclera",
|
||||
"expression": "evil smile",
|
||||
"hands": "black claws, pink nails",
|
||||
"arms": "",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "pink skin, long tail, pointy ears"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "black tube top",
|
||||
"outer_layer": "",
|
||||
"lower_body": "white harem pants",
|
||||
"footwear": "black and yellow boots",
|
||||
"gloves": "black sleeves",
|
||||
"accessories": "gold bracelets, gold neck ring, hoop earrings"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "supernatural, anime, dragon ball style",
|
||||
"primary_color": "pink",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "gold"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Dragon Ball FighterZ"
|
||||
]
|
||||
}
|
||||
39
characters/marin_kitagawa.json
Normal file
39
characters/marin_kitagawa.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "marin_kitagawa",
|
||||
"character_name": "Marin Kitagawa",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, fair skin, asian",
|
||||
"hair": "long blonde hair, pink tips",
|
||||
"eyes": "pink eyes (contacts)",
|
||||
"expression": "excited smile",
|
||||
"hands": "long pink nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "piercings"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "white school shirt, loosely tied blue tie",
|
||||
"lower_body": "blue plaid miniskirt",
|
||||
"footwear": "black loafers, black socks",
|
||||
"gloves": "",
|
||||
"accessories": "choker, various bracelets"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "gyaru, modern, anime style",
|
||||
"primary_color": "white",
|
||||
"secondary_color": "blue",
|
||||
"tertiary_color": "pink"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"My Dress-Up Darling"
|
||||
]
|
||||
}
|
||||
39
characters/megurine_luka.json
Normal file
39
characters/megurine_luka.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "megurine_luka",
|
||||
"character_name": "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": ""
|
||||
},
|
||||
"tags": [
|
||||
"Vocaloid"
|
||||
]
|
||||
}
|
||||
39
characters/meiko.json
Normal file
39
characters/meiko.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "meiko",
|
||||
"character_name": "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": ""
|
||||
},
|
||||
"tags": [
|
||||
"Vocaloid"
|
||||
]
|
||||
}
|
||||
39
characters/nessa.json
Normal file
39
characters/nessa.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "nessa",
|
||||
"character_name": "Nessa",
|
||||
"identity": {
|
||||
"base_specs": "1girl, athletic build, dark skin",
|
||||
"hair": "long hair, light blue highlights",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "confident smile",
|
||||
"hands": "blue nails",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "blue earrings"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "white and blue bikini top",
|
||||
"outer_layer": "gym uniform, number 049",
|
||||
"lower_body": "white and blue shorts",
|
||||
"footwear": "blue and white sandals",
|
||||
"gloves": "",
|
||||
"accessories": "wristband, life buoy, pokeball"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "sporty, aquatic, pokemon style",
|
||||
"primary_color": "blue",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "orange"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Pokemon"
|
||||
]
|
||||
}
|
||||
39
characters/olivier_mira_armstrong.json
Normal file
39
characters/olivier_mira_armstrong.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "olivier_mira_armstrong",
|
||||
"character_name": "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": ""
|
||||
},
|
||||
"tags": [
|
||||
"Fullmetal Alchemist"
|
||||
]
|
||||
}
|
||||
39
characters/princess_peach.json
Normal file
39
characters/princess_peach.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "princess_peach",
|
||||
"character_name": "Princess Peach",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "long blonde hair, voluminous, crown",
|
||||
"eyes": "blue eyes, long eyelashes",
|
||||
"expression": "gentle smile",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "pink lips, blue earrings"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "white petticoat",
|
||||
"outer_layer": "pink floor-length ball gown, puffy sleeves, dark pink panniers",
|
||||
"lower_body": "long skirt",
|
||||
"footwear": "red high heels",
|
||||
"gloves": "white opera gloves",
|
||||
"accessories": "gold crown with red and blue jewels, blue brooch"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "royal, whimsical, nintendo style",
|
||||
"primary_color": "pink",
|
||||
"secondary_color": "gold",
|
||||
"tertiary_color": "blue"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/Princess_Peach_Shiny_Style_V4.0_Illustrious_1652958.safetensors",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": "princess peach, crown, pink dress, shiny skin, royal elegance"
|
||||
},
|
||||
"tags": [
|
||||
"Super Mario"
|
||||
]
|
||||
}
|
||||
39
characters/princess_zelda_botw.json
Normal file
39
characters/princess_zelda_botw.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "princess_zelda_botw",
|
||||
"character_name": "Princess Zelda",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, fair skin, pointed ears",
|
||||
"hair": "long blonde hair, braided, gold hair clips",
|
||||
"eyes": "green eyes",
|
||||
"expression": "curious",
|
||||
"hands": "gold nails",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "tri-force symbol, elf ears"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "blue tunic",
|
||||
"outer_layer": "blue champion's tunic, brown leather belts",
|
||||
"lower_body": "tan trousers",
|
||||
"footwear": "brown leather boots",
|
||||
"gloves": "brown fingerless gloves",
|
||||
"accessories": "sheikah slate, gold jewelry"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "fantasy, adventurous, zelda style",
|
||||
"primary_color": "blue",
|
||||
"secondary_color": "gold",
|
||||
"tertiary_color": "brown"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"The Legend of Zelda"
|
||||
]
|
||||
}
|
||||
39
characters/rice_shower.json
Normal file
39
characters/rice_shower.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "rice_shower_(Umamusume)",
|
||||
"character_name": "Rice Shower",
|
||||
"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": ""
|
||||
},
|
||||
"tags": [
|
||||
"Umamusume"
|
||||
]
|
||||
}
|
||||
39
characters/riju.json
Normal file
39
characters/riju.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "riju",
|
||||
"character_name": "Riju",
|
||||
"identity": {
|
||||
"base_specs": "1girl, young, dark skin, gerudo",
|
||||
"hair": "short red hair, braided ponytail, gold hair ornament",
|
||||
"eyes": "green eyes",
|
||||
"expression": "serious",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "darkblue lipstick,"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "black top, blue sash",
|
||||
"lower_body": "black skirt, pelvic curtain,",
|
||||
"footwear": "gold high heels",
|
||||
"gloves": "",
|
||||
"accessories": "gold jewelry, earrings"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "fantasy, desert, gerudo style",
|
||||
"primary_color": "gold",
|
||||
"secondary_color": "black",
|
||||
"tertiary_color": "red"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"The Legend of Zelda"
|
||||
]
|
||||
}
|
||||
39
characters/rosalina.json
Normal file
39
characters/rosalina.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "rosalina",
|
||||
"character_name": "Rosalina",
|
||||
"identity": {
|
||||
"base_specs": "1girl, tall, slender build, fair skin",
|
||||
"hair": "long platinum blonde hair, side-swept bangs covering one eye",
|
||||
"eyes": "light blue eyes",
|
||||
"expression": "serene expression",
|
||||
"hands": "turquoise nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "star-shaped earrings"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "turquoise off-the-shoulder gown, silver trim",
|
||||
"lower_body": "long skirt",
|
||||
"footwear": "silver high heels",
|
||||
"gloves": "",
|
||||
"accessories": "silver crown with blue jewels, star wand, luma"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "celestial, elegant, nintendo style",
|
||||
"primary_color": "turquoise",
|
||||
"secondary_color": "silver",
|
||||
"tertiary_color": "yellow"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Super Mario"
|
||||
]
|
||||
}
|
||||
39
characters/rouge_the_bat.json
Normal file
39
characters/rouge_the_bat.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "rouge_the_bat",
|
||||
"character_name": "Rouge the Bat",
|
||||
"identity": {
|
||||
"base_specs": "1girl, anthro, bat girl, white fur",
|
||||
"hair": "short white hair",
|
||||
"eyes": "teal eyes",
|
||||
"expression": "sly smirk",
|
||||
"hands": "white gloves",
|
||||
"arms": "",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "bat wings, eyeshadow"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"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": "jewels, museum,sleek, spy, sonic style",
|
||||
"primary_color": "white",
|
||||
"secondary_color": "pink",
|
||||
"tertiary_color": "black"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "Illustrious/Looks/Rouge_the_bat_v2.safetensors",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Sonic the Hedgehog"
|
||||
]
|
||||
}
|
||||
39
characters/ryouko_hakubi.json
Normal file
39
characters/ryouko_hakubi.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "ryouko_(tenchi_muyou!)",
|
||||
"character_name": "Ryouko Hakubi",
|
||||
"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"
|
||||
},
|
||||
"tags": [
|
||||
"Tenchi Muyou!", "Tenchi Muyo!"
|
||||
]
|
||||
}
|
||||
39
characters/samus_aran_zero_suit.json
Normal file
39
characters/samus_aran_zero_suit.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "samus_aran",
|
||||
"character_name": "Samus Aran",
|
||||
"identity": {
|
||||
"base_specs": "1girl, athletic build, fair skin",
|
||||
"hair": "long blonde hair, ponytail",
|
||||
"eyes": "blue eyes",
|
||||
"expression": "serious expression",
|
||||
"hands": "blue nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "beauty mark on chin"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "blue skin-tight bodysuit, pink symbols",
|
||||
"lower_body": "bodysuit",
|
||||
"footwear": "blue high-heeled boots",
|
||||
"gloves": "zero suit",
|
||||
"accessories": "paralyzer pistol"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "sci-fi, sleek, metroid style",
|
||||
"primary_color": "blue",
|
||||
"secondary_color": "pink",
|
||||
"tertiary_color": "yellow"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Metroid"
|
||||
]
|
||||
}
|
||||
39
characters/sarah_miller.json
Normal file
39
characters/sarah_miller.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "sarah_miller_(the_last_of_us)",
|
||||
"character_name": "Sarah Miller",
|
||||
"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": ""
|
||||
},
|
||||
"tags": [
|
||||
"The Last of Us"
|
||||
]
|
||||
}
|
||||
39
characters/shantae.json
Normal file
39
characters/shantae.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "shantae",
|
||||
"character_name": "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": ""
|
||||
},
|
||||
"tags": [
|
||||
"Shantae"
|
||||
]
|
||||
}
|
||||
39
characters/sucy_manbavaran.json
Normal file
39
characters/sucy_manbavaran.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "sucy_manbavaran",
|
||||
"character_name": "Sucy Manbavaran",
|
||||
"identity": {
|
||||
"base_specs": "1girl, lanky build, pale skin",
|
||||
"hair": "light purple hair, hair covering one eye",
|
||||
"eyes": "red eyes",
|
||||
"expression": "deadpan expression",
|
||||
"hands": "black nails",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "narrow waist",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "dark circles under eyes"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "dark purple witch robes",
|
||||
"lower_body": "long skirt with frayed edges",
|
||||
"footwear": "brown boots",
|
||||
"gloves": "",
|
||||
"accessories": "pointed witch hat, potion bottle"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "gothic, whimsical, little witch academia style",
|
||||
"primary_color": "purple",
|
||||
"secondary_color": "mauve",
|
||||
"tertiary_color": "green"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Little Witch Academia"
|
||||
]
|
||||
}
|
||||
39
characters/tifa_lockhart.json
Normal file
39
characters/tifa_lockhart.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "tifa_lockhart",
|
||||
"character_name": "Tifa Lockhart",
|
||||
"identity": {
|
||||
"base_specs": "1girl, athletic build, fair skin",
|
||||
"hair": "long black hair, tied end",
|
||||
"eyes": "red eyes",
|
||||
"expression": "kind smile",
|
||||
"hands": "dark red nails",
|
||||
"arms": "",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "black sports bra",
|
||||
"outer_layer": "white tank top, black suspenders",
|
||||
"lower_body": "black miniskirt",
|
||||
"footwear": "red boots, black socks",
|
||||
"gloves": "red fingerless gloves",
|
||||
"accessories": "silver earrings"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "urban, martial arts, final fantasy style",
|
||||
"primary_color": "white",
|
||||
"secondary_color": "black",
|
||||
"tertiary_color": "red"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Final Fantasy VII"
|
||||
]
|
||||
}
|
||||
39
characters/tracer.json
Normal file
39
characters/tracer.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "tracer",
|
||||
"character_name": "Tracer",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "short spiky brown hair",
|
||||
"eyes": "brown eyes",
|
||||
"expression": "energetic smile",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "freckles"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "orange leggings",
|
||||
"outer_layer": "brown flight jacket, yellow vest",
|
||||
"lower_body": "orange leggings",
|
||||
"footwear": "white and orange sneakers",
|
||||
"gloves": "",
|
||||
"accessories": "chronal accelerator, yellow goggles"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "sci-fi, pilot, overwatch style",
|
||||
"primary_color": "orange",
|
||||
"secondary_color": "brown",
|
||||
"tertiary_color": "white"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Overwatch"
|
||||
]
|
||||
}
|
||||
39
characters/urbosa.json
Normal file
39
characters/urbosa.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "urbosa",
|
||||
"character_name": "Urbosa",
|
||||
"identity": {
|
||||
"base_specs": "1girl, tall, muscular, dark skin, gerudo",
|
||||
"hair": "long red hair, wild hair",
|
||||
"eyes": "green eyes",
|
||||
"expression": "confident",
|
||||
"hands": "gold nails",
|
||||
"arms": "muscular arms",
|
||||
"torso": "abs, mediumS breasts",
|
||||
"pelvis": "wide hips",
|
||||
"legs": "muscular legs",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "dark blue lipstick, gerudo markings"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"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"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "fantasy, warrior, gerudo style",
|
||||
"primary_color": "gold",
|
||||
"secondary_color": "blue",
|
||||
"tertiary_color": "red"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"The Legend of Zelda"
|
||||
]
|
||||
}
|
||||
39
characters/widowmaker.json
Normal file
39
characters/widowmaker.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "widowmaker",
|
||||
"character_name": "Widowmaker",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, blue skin",
|
||||
"hair": "long purple hair, ponytail",
|
||||
"eyes": "yellow eyes",
|
||||
"expression": "cold expression",
|
||||
"hands": "",
|
||||
"arms": "spider tattoo on arm",
|
||||
"torso": "large breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "blue skin"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "purple tactical bodysuit, plunging neckline",
|
||||
"lower_body": "bodysuit",
|
||||
"footwear": "purple high-heeled boots",
|
||||
"gloves": "purple gauntlets",
|
||||
"accessories": "sniper visor, grappling hook"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "sci-fi, assassin, overwatch style",
|
||||
"primary_color": "purple",
|
||||
"secondary_color": "black",
|
||||
"tertiary_color": "pink"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Overwatch"
|
||||
]
|
||||
}
|
||||
39
characters/yor_briar.json
Normal file
39
characters/yor_briar.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "yor_briar",
|
||||
"character_name": "Yor Briar",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "long black hair, styled with gold headband",
|
||||
"eyes": "red eyes",
|
||||
"expression": "gentle yet mysterious smile",
|
||||
"hands": "black nails",
|
||||
"arms": "",
|
||||
"torso": "medium breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "black backless halter dress, red rose pattern inside",
|
||||
"lower_body": "black thigh-high boots",
|
||||
"footwear": "black boots",
|
||||
"gloves": "black fingerless gloves",
|
||||
"accessories": "gold rose-themed headband, gold needle weapons"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "elegant, assassin, spy x family style",
|
||||
"primary_color": "black",
|
||||
"secondary_color": "red",
|
||||
"tertiary_color": "gold"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Spy x Family"
|
||||
]
|
||||
}
|
||||
39
characters/yshtola_rhul.json
Normal file
39
characters/yshtola_rhul.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "y'shtola_rhul",
|
||||
"character_name": "Y'shtola Rhul",
|
||||
"identity": {
|
||||
"base_specs": "1girl, miqo'te, slender build, fair skin, cat ears",
|
||||
"hair": "short white hair, bangs",
|
||||
"eyes": "blind, white eyes",
|
||||
"expression": "stoic expression",
|
||||
"hands": "black nails",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "facial markings, cat tail"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "black sorceress robes, fur trim",
|
||||
"lower_body": "long skirt",
|
||||
"footwear": "black boots",
|
||||
"gloves": "",
|
||||
"accessories": "wooden staff"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "magical, scholarly, final fantasy xiv style",
|
||||
"primary_color": "black",
|
||||
"secondary_color": "white",
|
||||
"tertiary_color": "purple"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Final Fantasy XIV"
|
||||
]
|
||||
}
|
||||
39
characters/yuffie_kisaragi.json
Normal file
39
characters/yuffie_kisaragi.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "yuffie_kisaragi",
|
||||
"character_name": "Yuffie Kisaragi",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender build, fair skin",
|
||||
"hair": "short black hair, bob cut",
|
||||
"eyes": "brown eyes",
|
||||
"expression": "playful grin",
|
||||
"hands": "",
|
||||
"arms": "black sleeve on one arm",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": "headband"
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "",
|
||||
"outer_layer": "green turtleneck sweater vest, midriff",
|
||||
"lower_body": "beige shorts",
|
||||
"footwear": "boots, socks",
|
||||
"gloves": "fingerless glove on one hand, large gauntlet on one arm",
|
||||
"accessories": "shuriken"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "ninja, adventurer, final fantasy style",
|
||||
"primary_color": "green",
|
||||
"secondary_color": "beige",
|
||||
"tertiary_color": "black"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 1.0,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Final Fantasy VII"
|
||||
]
|
||||
}
|
||||
39
characters/yuna_ffx.json
Normal file
39
characters/yuna_ffx.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"character_id": "yuna_(ff10)",
|
||||
"character_name": "Yuna",
|
||||
"identity": {
|
||||
"base_specs": "1girl, slender, fair skin",
|
||||
"hair": "short brown hair, bob cut",
|
||||
"eyes": "heterochromia, blue eye, green eye",
|
||||
"expression": "gentle",
|
||||
"hands": "",
|
||||
"arms": "",
|
||||
"torso": "small breasts",
|
||||
"pelvis": "",
|
||||
"legs": "",
|
||||
"feet": "",
|
||||
"distinguishing_marks": ""
|
||||
},
|
||||
"wardrobe": {
|
||||
"inner_layer": "white kimono top, yellow obi",
|
||||
"outer_layer": "",
|
||||
"lower_body": "long blue skirt, floral pattern",
|
||||
"footwear": "boots",
|
||||
"gloves": "detached sleeves",
|
||||
"accessories": "summoner staff, necklace"
|
||||
},
|
||||
"styles": {
|
||||
"aesthetic": "fantasy, final fantasy x style",
|
||||
"primary_color": "white",
|
||||
"secondary_color": "blue",
|
||||
"tertiary_color": "yellow"
|
||||
},
|
||||
"lora": {
|
||||
"lora_name": "",
|
||||
"lora_weight": 0.8,
|
||||
"lora_triggers": ""
|
||||
},
|
||||
"tags": [
|
||||
"Final Fantasy X"
|
||||
]
|
||||
}
|
||||
173
comfy_workflow.json
Normal file
173
comfy_workflow.json
Normal file
@@ -0,0 +1,173 @@
|
||||
{
|
||||
"3": {
|
||||
"inputs": {
|
||||
"seed": 8566257,
|
||||
"steps": 20,
|
||||
"cfg": 3.5,
|
||||
"sampler_name": "euler_ancestral",
|
||||
"scheduler": "normal",
|
||||
"denoise": 1,
|
||||
"model": ["4", 0],
|
||||
"positive": ["6", 0],
|
||||
"negative": ["7", 0],
|
||||
"latent_image": ["5", 0]
|
||||
},
|
||||
"class_type": "KSampler"
|
||||
},
|
||||
"4": {
|
||||
"inputs": {
|
||||
"ckpt_name": "Noob/oneObsession_v19Atypical.safetensors"
|
||||
},
|
||||
"class_type": "CheckpointLoaderSimple"
|
||||
},
|
||||
"5": {
|
||||
"inputs": {
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"batch_size": 1
|
||||
},
|
||||
"class_type": "EmptyLatentImage"
|
||||
},
|
||||
"6": {
|
||||
"inputs": {
|
||||
"text": "embedding:Illustrious/lazypos_1733353, {{POSITIVE_PROMPT}}",
|
||||
"clip": ["4", 1]
|
||||
},
|
||||
"class_type": "CLIPTextEncode"
|
||||
},
|
||||
"7": {
|
||||
"inputs": {
|
||||
"text": "embedding:Illustrious/lazyneg_1760455, embedding:Illustrious/lazyhand",
|
||||
"clip": ["4", 1]
|
||||
},
|
||||
"class_type": "CLIPTextEncode"
|
||||
},
|
||||
"8": {
|
||||
"inputs": {
|
||||
"samples": ["3", 0],
|
||||
"vae": ["4", 2]
|
||||
},
|
||||
"class_type": "VAEDecode"
|
||||
},
|
||||
"9": {
|
||||
"inputs": {
|
||||
"filename_prefix": "ComfyUI",
|
||||
"images": ["13", 0]
|
||||
},
|
||||
"class_type": "SaveImage"
|
||||
},
|
||||
"10": {
|
||||
"inputs": {
|
||||
"model_name": "bbox/face_yolov9c.pt"
|
||||
},
|
||||
"class_type": "UltralyticsDetectorProvider"
|
||||
},
|
||||
"11": {
|
||||
"inputs": {
|
||||
"guide_size": 384,
|
||||
"guide_size_for": "bbox",
|
||||
"max_size": 1024,
|
||||
"seed": 123456,
|
||||
"steps": 20,
|
||||
"cfg": 3.5,
|
||||
"sampler_name": "euler_ancestral",
|
||||
"scheduler": "normal",
|
||||
"denoise": 0.5,
|
||||
"feather": 5,
|
||||
"noise_mask": true,
|
||||
"force_inpaint": true,
|
||||
"bbox_threshold": 0.5,
|
||||
"bbox_dilation": 10,
|
||||
"bbox_crop_factor": 3,
|
||||
"sam_threshold": 0.6,
|
||||
"sam_dilation": 0,
|
||||
"sam_mask_hint_threshold": 0.7,
|
||||
"sam_mask_hint_use_negative": "False",
|
||||
"sam_bbox_expansion": 0,
|
||||
"sam_detection_hint": "none",
|
||||
"sam_mask_hint_area": "bbox",
|
||||
"drop_size": 10,
|
||||
"wildcard": "",
|
||||
"cycle": 1,
|
||||
"inpaint_model": false,
|
||||
"noise_mask_feather": 20,
|
||||
"image": ["8", 0],
|
||||
"model": ["4", 0],
|
||||
"clip": ["4", 1],
|
||||
"vae": ["4", 2],
|
||||
"positive": ["14", 0],
|
||||
"negative": ["7", 0],
|
||||
"bbox_detector": ["10", 0]
|
||||
},
|
||||
"class_type": "FaceDetailer"
|
||||
},
|
||||
"12": {
|
||||
"inputs": {
|
||||
"model_name": "bbox/hand_yolov8s.pt"
|
||||
},
|
||||
"class_type": "UltralyticsDetectorProvider"
|
||||
},
|
||||
"13": {
|
||||
"inputs": {
|
||||
"guide_size": 384,
|
||||
"guide_size_for": "bbox",
|
||||
"max_size": 1024,
|
||||
"seed": 123456,
|
||||
"steps": 20,
|
||||
"cfg": 3.5,
|
||||
"sampler_name": "euler_ancestral",
|
||||
"scheduler": "normal",
|
||||
"denoise": 0.5,
|
||||
"feather": 5,
|
||||
"noise_mask": true,
|
||||
"force_inpaint": true,
|
||||
"bbox_threshold": 0.5,
|
||||
"bbox_dilation": 10,
|
||||
"bbox_crop_factor": 3,
|
||||
"sam_threshold": 0.6,
|
||||
"sam_dilation": 0,
|
||||
"sam_mask_hint_threshold": 0.7,
|
||||
"sam_mask_hint_use_negative": "False",
|
||||
"sam_bbox_expansion": 0,
|
||||
"sam_detection_hint": "none",
|
||||
"sam_mask_hint_area": "bbox",
|
||||
"drop_size": 10,
|
||||
"wildcard": "",
|
||||
"cycle": 1,
|
||||
"inpaint_model": false,
|
||||
"noise_mask_feather": 20,
|
||||
"image": ["11", 0],
|
||||
"model": ["4", 0],
|
||||
"clip": ["4", 1],
|
||||
"vae": ["4", 2],
|
||||
"positive": ["15", 0],
|
||||
"negative": ["7", 0],
|
||||
"bbox_detector": ["12", 0]
|
||||
},
|
||||
"class_type": "FaceDetailer"
|
||||
},
|
||||
"14": {
|
||||
"inputs": {
|
||||
"text": "{{FACE_PROMPT}}",
|
||||
"clip": ["4", 1]
|
||||
},
|
||||
"class_type": "CLIPTextEncode"
|
||||
},
|
||||
"15": {
|
||||
"inputs": {
|
||||
"text": "{{HAND_PROMPT}}",
|
||||
"clip": ["4", 1]
|
||||
},
|
||||
"class_type": "CLIPTextEncode"
|
||||
},
|
||||
"16": {
|
||||
"inputs": {
|
||||
"lora_name": "",
|
||||
"strength_model": 1.0,
|
||||
"strength_clip": 1.0,
|
||||
"model": ["4", 0],
|
||||
"clip": ["4", 1]
|
||||
},
|
||||
"class_type": "LoraLoader"
|
||||
}
|
||||
}
|
||||
32
launch.sh
Normal file
32
launch.sh
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Exit on error
|
||||
set -e
|
||||
|
||||
VENV_DIR="venv"
|
||||
|
||||
echo "Checking for virtual environment..."
|
||||
if [ ! -d "$VENV_DIR" ]; then
|
||||
echo "Creating virtual environment..."
|
||||
python3 -m venv "$VENV_DIR"
|
||||
fi
|
||||
|
||||
echo "Activating virtual environment..."
|
||||
source "$VENV_DIR/bin/activate"
|
||||
|
||||
if [ "$1" == "--clean" ]; then
|
||||
echo "Performing clean start..."
|
||||
echo "Removing database..."
|
||||
rm -f database.db
|
||||
echo "Clearing uploads..."
|
||||
rm -rf static/uploads/*
|
||||
fi
|
||||
|
||||
echo "Upgrading pip and setuptools..."
|
||||
pip install --upgrade pip setuptools wheel
|
||||
|
||||
echo "Installing/Updating requirements..."
|
||||
pip install -r requirements.txt
|
||||
|
||||
echo "Starting Character Browser..."
|
||||
python3 app.py
|
||||
15
models.py
Normal file
15
models.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
class Character(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
character_id = db.Column(db.String(100), unique=True, nullable=False)
|
||||
slug = db.Column(db.String(100), unique=True, nullable=False)
|
||||
name = db.Column(db.String(100), nullable=False)
|
||||
data = db.Column(db.JSON, nullable=False)
|
||||
default_fields = db.Column(db.JSON, nullable=True)
|
||||
image_path = db.Column(db.String(255), nullable=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Character {self.character_id}>'
|
||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Flask
|
||||
Flask-SQLAlchemy
|
||||
Pillow
|
||||
requests
|
||||
288
templates/detail.html
Normal file
288
templates/detail.html
Normal file
@@ -0,0 +1,288 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="card mb-4">
|
||||
<div class="img-container" style="height: auto; min-height: 400px;">
|
||||
{% if character.image_path %}
|
||||
<img src="{{ url_for('static', filename='uploads/' + character.image_path) }}" alt="{{ character.name }}" class="img-fluid">
|
||||
{% else %}
|
||||
<span class="text-muted">No Image Attached</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ url_for('upload_image', slug=character.slug) }}" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="image" class="form-label">Update Image</label>
|
||||
<input class="form-control" type="file" id="image" name="image" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100 mb-2">Upload</button>
|
||||
</form>
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" name="action" value="preview" class="btn btn-success" form="generate-form">Generate Preview</button>
|
||||
<button type="submit" name="action" value="replace" class="btn btn-outline-danger" form="generate-form">Generate & Replace Cover</button>
|
||||
<button type="submit" form="generate-form" formaction="{{ url_for('save_defaults', slug=character.slug) }}" class="btn btn-sm btn-outline-secondary mt-2">Save as Default Selection</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="progress-container" class="mb-4 d-none">
|
||||
<label id="progress-label" class="form-label">Generating...</label>
|
||||
<div class="progress" role="progressbar" aria-label="Generation Progress" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
|
||||
<div id="progress-bar" class="progress-bar progress-bar-striped progress-bar-animated" style="width: 0%">0%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if preview_image %}
|
||||
<div class="card mb-4 border-success">
|
||||
<div class="card-header bg-success text-white">Latest Preview</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="img-container" style="height: auto; min-height: 400px;">
|
||||
<img id="preview-img" src="{{ url_for('static', filename='uploads/' + preview_image) }}" alt="Preview" class="img-fluid">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="card mb-4 border-secondary d-none" id="preview-card">
|
||||
<div class="card-header bg-secondary text-white">Latest Preview</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="img-container" style="height: auto; min-height: 400px;">
|
||||
<img id="preview-img" src="" alt="Preview" class="img-fluid">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-dark text-white d-flex justify-content-between align-items-center">
|
||||
<span>Tags</span>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="include_field" value="special::tags" id="includeTags" form="generate-form"
|
||||
{% if preferences is not none %}
|
||||
{% if 'special::tags' in preferences %}checked{% endif %}
|
||||
{% elif character.default_fields is not none %}
|
||||
{% if 'special::tags' in character.default_fields %}checked{% endif %}
|
||||
{% else %}
|
||||
checked
|
||||
{% endif %}>
|
||||
<label class="form-check-label text-white small" for="includeTags">Include</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% for tag in character.data.tags %}
|
||||
<span class="badge bg-secondary">{{ tag }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>{{ character.name }}</h1>
|
||||
<a href="/" class="btn btn-outline-secondary">Back to Gallery</a>
|
||||
</div>
|
||||
|
||||
<form id="generate-form" action="{{ url_for('generate_image', slug=character.slug) }}" method="post">
|
||||
{% for section, details in character.data.items() %}
|
||||
{% if section not in ['character_id', 'tags', 'name'] and details is mapping %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light text-capitalize"><strong>{{ section.replace('_', ' ') }}</strong></div>
|
||||
<div class="card-body">
|
||||
<dl class="row mb-0">
|
||||
{% if section == 'identity' %}
|
||||
<dt class="col-sm-4 text-capitalize">
|
||||
<input class="form-check-input me-1" type="checkbox" name="include_field" value="special::name"
|
||||
{% if preferences is not none %}
|
||||
{% if 'special::name' in preferences %}checked{% endif %}
|
||||
{% elif character.default_fields is not none %}
|
||||
{% if 'special::name' in character.default_fields %}checked{% endif %}
|
||||
{% else %}
|
||||
checked
|
||||
{% endif %}>
|
||||
Character ID
|
||||
</dt>
|
||||
<dd class="col-sm-8">{{ character.character_id }}</dd>
|
||||
{% endif %}
|
||||
|
||||
{% for key, value in details.items() %}
|
||||
<dt class="col-sm-4 text-capitalize">
|
||||
<input class="form-check-input me-1" type="checkbox" name="include_field" value="{{ section }}::{{ key }}"
|
||||
{% if preferences is not none %}
|
||||
{% if section + '::' + key in preferences %}checked{% endif %}
|
||||
{% elif character.default_fields is not none %}
|
||||
{% if section + '::' + key in character.default_fields %}checked{% endif %}
|
||||
{% else %}
|
||||
{% if value %}checked{% endif %}
|
||||
{% endif %}>
|
||||
{{ key.replace('_', ' ') }}
|
||||
</dt>
|
||||
<dd class="col-sm-8">{{ value if value else '--' }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const form = document.getElementById('generate-form');
|
||||
const progressBar = document.getElementById('progress-bar');
|
||||
const progressContainer = document.getElementById('progress-container');
|
||||
const progressLabel = document.getElementById('progress-label');
|
||||
const previewCard = document.getElementById('preview-card');
|
||||
const previewImg = document.getElementById('preview-img');
|
||||
|
||||
// Generate a unique client ID
|
||||
const clientId = 'detail_view_' + Math.random().toString(36).substring(2, 15);
|
||||
|
||||
// ComfyUI WebSocket
|
||||
const socket = new WebSocket(`ws://127.0.0.1:8188/ws?clientId=${clientId}`);
|
||||
|
||||
let currentPromptId = null;
|
||||
let currentAction = null;
|
||||
|
||||
socket.addEventListener('message', (event) => {
|
||||
if (!currentPromptId) return;
|
||||
|
||||
const msg = JSON.parse(event.data);
|
||||
|
||||
if (msg.type === 'status') {
|
||||
const queueRemaining = msg.data.status.exec_info.queue_remaining;
|
||||
if (queueRemaining > 0) {
|
||||
progressLabel.textContent = `Queue position: ${queueRemaining}`;
|
||||
}
|
||||
}
|
||||
else if (msg.type === 'progress') {
|
||||
const value = msg.data.value;
|
||||
const max = msg.data.max;
|
||||
const percent = Math.round((value / max) * 100);
|
||||
progressBar.style.width = `${percent}%`;
|
||||
progressBar.textContent = `${percent}%`;
|
||||
}
|
||||
else if (msg.type === 'executing') {
|
||||
if (msg.data.node === null && msg.data.prompt_id === currentPromptId) {
|
||||
// Execution finished via WebSocket
|
||||
console.log('Finished via WebSocket');
|
||||
if (resolveCompletion) resolveCompletion();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let resolveCompletion = null;
|
||||
async function waitForCompletion(promptId) {
|
||||
return new Promise((resolve) => {
|
||||
const checkResolve = () => {
|
||||
clearInterval(pollInterval);
|
||||
resolve();
|
||||
};
|
||||
resolveCompletion = checkResolve;
|
||||
|
||||
// Fallback polling in case WebSocket is blocked (403)
|
||||
const pollInterval = setInterval(async () => {
|
||||
try {
|
||||
const resp = await fetch(`/check_status/${promptId}`);
|
||||
const data = await resp.json();
|
||||
if (data.status === 'finished') {
|
||||
console.log('Finished via Polling');
|
||||
checkResolve();
|
||||
}
|
||||
} catch (err) { console.error('Polling error:', err); }
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
// Only intercept generate actions
|
||||
const submitter = e.submitter;
|
||||
if (!submitter || (submitter.value !== 'preview' && submitter.value !== 'replace')) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
currentAction = submitter.value;
|
||||
const formData = new FormData(form);
|
||||
formData.append('action', currentAction);
|
||||
formData.append('client_id', clientId);
|
||||
|
||||
// UI Reset
|
||||
progressContainer.classList.remove('d-none');
|
||||
progressBar.style.width = '0%';
|
||||
progressBar.textContent = '0%';
|
||||
progressLabel.textContent = 'Starting...';
|
||||
|
||||
try {
|
||||
const response = await fetch(form.action, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
alert('Error: ' + data.error);
|
||||
progressContainer.classList.add('d-none');
|
||||
return;
|
||||
}
|
||||
|
||||
currentPromptId = data.prompt_id;
|
||||
progressLabel.textContent = 'Queued...';
|
||||
|
||||
// Wait for completion (WebSocket or Polling)
|
||||
await waitForCompletion(currentPromptId);
|
||||
|
||||
// Finalize
|
||||
finalizeGeneration(currentPromptId, currentAction);
|
||||
currentPromptId = null;
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert('Request failed');
|
||||
progressContainer.classList.add('d-none');
|
||||
}
|
||||
});
|
||||
|
||||
async function finalizeGeneration(promptId, action) {
|
||||
progressLabel.textContent = 'Saving image...';
|
||||
const url = `/character/{{ character.slug }}/finalize_generation/${promptId}`;
|
||||
const formData = new FormData();
|
||||
formData.append('action', action);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
if (action === 'preview') {
|
||||
previewImg.src = data.image_url;
|
||||
if (previewCard) previewCard.classList.remove('d-none');
|
||||
} else {
|
||||
// Reload for cover update
|
||||
window.location.reload();
|
||||
}
|
||||
} else {
|
||||
alert('Save failed: ' + data.error);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert('Finalize request failed');
|
||||
} finally {
|
||||
progressContainer.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
70
templates/generator.html
Normal file
70
templates/generator.html
Normal file
@@ -0,0 +1,70 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">Generator Settings</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ url_for('generator') }}" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="character" class="form-label">Character</label>
|
||||
<select class="form-select" id="character" name="character" required>
|
||||
<option value="" disabled {% if not selected_char %}selected{% endif %}>Select a character...</option>
|
||||
{% for char in characters %}
|
||||
<option value="{{ char.slug }}" {% if selected_char == char.slug %}selected{% endif %}>{{ char.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="checkpoint" class="form-label">Checkpoint Model</label>
|
||||
<select class="form-select" id="checkpoint" name="checkpoint" required>
|
||||
{% for ckpt in checkpoints %}
|
||||
<option value="{{ ckpt }}" {% if selected_ckpt == ckpt %}selected{% endif %}>{{ ckpt }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="form-text">Listing models from Illustrious/ folder</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="positive_prompt" class="form-label">Additional Positive Prompt</label>
|
||||
<textarea class="form-control" id="positive_prompt" name="positive_prompt" rows="3" placeholder="e.g. sitting in a cafe, drinking coffee, daylight"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="negative_prompt" class="form-label">Additional Negative Prompt</label>
|
||||
<textarea class="form-control" id="negative_prompt" name="negative_prompt" rows="3" placeholder="e.g. bad hands, extra digits"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100">Generate</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-7">
|
||||
<div class="card">
|
||||
<div class="card-header bg-dark text-white">Result</div>
|
||||
<div class="card-body p-0 d-flex align-items-center justify-content-center" style="min-height: 500px; background-color: #eee;">
|
||||
{% if generated_image %}
|
||||
<div class="img-container w-100 h-100">
|
||||
<img src="{{ url_for('static', filename='uploads/' + generated_image) }}" alt="Generated Result" class="img-fluid w-100">
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center text-muted">
|
||||
<p>Select settings and click Generate</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if generated_image %}
|
||||
<div class="card-footer">
|
||||
<small class="text-muted">Saved to character gallery</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
178
templates/index.html
Normal file
178
templates/index.html
Normal file
@@ -0,0 +1,178 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Character Gallery</h2>
|
||||
<div class="d-flex">
|
||||
<button id="batch-generate-btn" class="btn btn-outline-success me-2">Generate Missing Covers</button>
|
||||
<button id="regenerate-all-btn" class="btn btn-outline-danger me-2">Regenerate All Covers</button>
|
||||
<form action="{{ url_for('rescan') }}" method="post">
|
||||
<button type="submit" class="btn btn-outline-primary">Rescan Character Files</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Batch Progress Bar -->
|
||||
<div id="batch-progress-container" class="card mb-4 d-none">
|
||||
<div class="card-body">
|
||||
<h5 id="batch-status-text">Batch Generating...</h5>
|
||||
<div class="progress mt-2" role="progressbar" aria-label="Batch Progress" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
|
||||
<div id="batch-progress-bar" class="progress-bar progress-bar-striped progress-bar-animated bg-success" style="width: 0%">0%</div>
|
||||
</div>
|
||||
<p id="current-char-name" class="small text-muted mt-2 mb-0"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-4 g-4">
|
||||
{% for char in characters %}
|
||||
<div class="col" id="card-{{ char.slug }}">
|
||||
<div class="card h-100 character-card" onclick="window.location.href='/character/{{ char.slug }}'">
|
||||
<div class="img-container">
|
||||
{% if char.image_path %}
|
||||
<img id="img-{{ char.slug }}" src="{{ url_for('static', filename='uploads/' + char.image_path) }}" alt="{{ char.name }}">
|
||||
<span id="no-img-{{ char.slug }}" class="text-muted d-none">No Image</span>
|
||||
{% else %}
|
||||
<img id="img-{{ char.slug }}" src="" alt="{{ char.name }}" class="d-none">
|
||||
<span id="no-img-{{ char.slug }}" class="text-muted">No Image</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-center">{{ char.name }}</h5>
|
||||
<p class="card-text small text-center text-muted">{{ char.data.tags | join(', ') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const batchBtn = document.getElementById('batch-generate-btn');
|
||||
const regenAllBtn = document.getElementById('regenerate-all-btn');
|
||||
const progressBar = document.getElementById('batch-progress-bar');
|
||||
const container = document.getElementById('batch-progress-container');
|
||||
const statusText = document.getElementById('batch-status-text');
|
||||
const charNameText = document.getElementById('current-char-name');
|
||||
|
||||
const clientId = 'gallery_batch_' + Math.random().toString(36).substring(2, 15);
|
||||
const socket = new WebSocket(`ws://127.0.0.1:8188/ws?clientId=${clientId}`);
|
||||
|
||||
let currentPromptId = null;
|
||||
let resolveGeneration = null;
|
||||
|
||||
socket.addEventListener('message', (event) => {
|
||||
const msg = JSON.parse(event.data);
|
||||
if (msg.type === 'executing') {
|
||||
if (msg.data.node === null && msg.data.prompt_id === currentPromptId) {
|
||||
if (resolveGeneration) resolveGeneration();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function waitForCompletion(promptId) {
|
||||
return new Promise((resolve) => {
|
||||
const checkResolve = () => {
|
||||
clearInterval(pollInterval);
|
||||
resolve();
|
||||
};
|
||||
resolveGeneration = checkResolve;
|
||||
const pollInterval = setInterval(async () => {
|
||||
try {
|
||||
const resp = await fetch(`/check_status/${promptId}`);
|
||||
const data = await resp.json();
|
||||
if (data.status === 'finished') {
|
||||
checkResolve();
|
||||
}
|
||||
} catch (err) {}
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
async function runBatch() {
|
||||
const response = await fetch('/get_missing_characters');
|
||||
const data = await response.json();
|
||||
const missing = data.missing;
|
||||
|
||||
if (missing.length === 0) {
|
||||
alert("No characters missing cover images.");
|
||||
return;
|
||||
}
|
||||
|
||||
batchBtn.disabled = true;
|
||||
regenAllBtn.disabled = true;
|
||||
container.classList.remove('d-none');
|
||||
|
||||
let completed = 0;
|
||||
for (const char of missing) {
|
||||
completed++;
|
||||
const percent = Math.round((completed / missing.length) * 100);
|
||||
progressBar.style.width = `${percent}%`;
|
||||
progressBar.textContent = `${percent}%`;
|
||||
statusText.textContent = `Batch Generating: ${completed} / ${missing.length}`;
|
||||
charNameText.textContent = `Current: ${char.name}`;
|
||||
|
||||
try {
|
||||
const genResp = await fetch(`/character/${char.slug}/generate`, {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams({ 'action': 'replace', 'client_id': clientId }),
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
});
|
||||
const genData = await genResp.json();
|
||||
currentPromptId = genData.prompt_id;
|
||||
|
||||
await waitForCompletion(currentPromptId);
|
||||
|
||||
const finResp = await fetch(`/character/${char.slug}/finalize_generation/${currentPromptId}`, {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams({ 'action': 'replace' })
|
||||
});
|
||||
const finData = await finResp.json();
|
||||
|
||||
if (finData.success) {
|
||||
const img = document.getElementById(`img-${char.slug}`);
|
||||
const noImgSpan = document.getElementById(`no-img-${char.slug}`);
|
||||
if (img) {
|
||||
img.src = finData.image_url;
|
||||
img.classList.remove('d-none');
|
||||
}
|
||||
if (noImgSpan) noImgSpan.classList.add('d-none');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Failed for ${char.name}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
statusText.textContent = "Batch Complete!";
|
||||
charNameText.textContent = "";
|
||||
batchBtn.disabled = false;
|
||||
regenAllBtn.disabled = false;
|
||||
setTimeout(() => { container.classList.add('d-none'); }, 5000);
|
||||
}
|
||||
|
||||
batchBtn.addEventListener('click', async () => {
|
||||
const response = await fetch('/get_missing_characters');
|
||||
const data = await response.json();
|
||||
if (data.missing.length === 0) {
|
||||
alert("No characters missing cover images.");
|
||||
return;
|
||||
}
|
||||
if (!confirm(`Generate cover images for ${data.missing.length} characters?`)) return;
|
||||
runBatch();
|
||||
});
|
||||
|
||||
regenAllBtn.addEventListener('click', async () => {
|
||||
if (!confirm("This will unassign ALL current cover images and generate new ones for every character. Existing files will be kept on disk. Proceed?")) return;
|
||||
|
||||
const clearResp = await fetch('/clear_all_covers', { method: 'POST' });
|
||||
if (clearResp.ok) {
|
||||
// Update UI to show "No Image" for all
|
||||
document.querySelectorAll('.img-container img').forEach(img => img.classList.add('d-none'));
|
||||
document.querySelectorAll('.img-container .text-muted').forEach(span => span.classList.remove('d-none'));
|
||||
runBatch();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
41
templates/layout.html
Normal file
41
templates/layout.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Character Browser</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body { background-color: #f8f9fa; }
|
||||
.character-card { transition: transform 0.2s; cursor: pointer; }
|
||||
.character-card:hover { transform: scale(1.02); }
|
||||
.img-container { height: 300px; overflow: hidden; background-color: #dee2e6; display: flex; align-items: center; justify-content: center; }
|
||||
.img-container img { width: 100%; height: 100%; object-fit: cover; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-dark bg-dark mb-4">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/">Character Browser</a>
|
||||
<div class="d-flex">
|
||||
<a href="/generator" class="btn btn-outline-light me-2">Generator</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container">
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-info">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user