File size: 8,789 Bytes
bb85dc7
3ee4725
 
04317d3
 
fee61cb
17d34fe
 
04317d3
fee61cb
17d34fe
fee61cb
3ee4725
 
 
 
 
 
 
 
 
fee61cb
212866f
 
 
bb85dc7
 
 
 
04317d3
fee61cb
 
04317d3
17d34fe
2fdefdd
17d34fe
 
 
 
 
2fdefdd
17d34fe
 
fee61cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17d34fe
fee61cb
 
 
 
 
 
 
 
 
 
 
17d34fe
 
fee61cb
 
2fdefdd
17d34fe
 
2fdefdd
17d34fe
2fdefdd
17d34fe
 
2fdefdd
 
 
 
 
10ed116
 
04317d3
2fdefdd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fee61cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
04317d3
 
 
 
 
 
fee61cb
 
 
 
 
 
04317d3
17d34fe
 
 
fee61cb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import os
import sys
import zipfile
import gradio as gr
import numpy as np
from PIL import Image, PngImagePlugin
import asyncio
import io
import json
import re
import random
import base64

# Unzip the novelai_api package if it's not already extracted
if not os.path.exists("novelai_api"):
    with zipfile.ZipFile("novelai_api.zip", "r") as zip_ref:
        zip_ref.extractall(".")

# Add the extracted directory to Python path
sys.path.append(os.path.abspath("novelai_api"))

# Import NovelAIAPI
from novelai_api import NovelAIAPI
from novelai_api.ImagePreset import ImageModel, ImageGenerationType

# Retrieve the NovelAI API token from environment variables
ACCESS_TOKEN = os.getenv("NOVELAI_ACCESS_TOKEN")
if ACCESS_TOKEN is None:
    raise ValueError("API token not found. Please set it as a secret in Hugging Face settings.")

# Paths to JSON data files
ARTISTS_PATH = 'artists.json'
CHARACTERS_PATH = 'characters.json'

# Load data from a JSON file
def load_json_file(file_path):
    try:
        with open(file_path, 'r') as file:
            return json.load(file)
    except Exception as e:
        print(f"Error reading {file_path}: {e}")
        return []

# Parse metadata from an image file
def parse_image_metadata(image_path):
    params = {}
    try:
        with Image.open(image_path) as img:
            if isinstance(img.info, PngImagePlugin.PngInfo):
                metadata = img.info.get("parameters")
                if metadata:
                    metadata_json = json.loads(metadata)
                    params['prompt'] = metadata_json.get("prompt", "")
                    params['steps'] = metadata_json.get("steps", 28)
                    params['height'] = metadata_json.get("height", 832)
                    params['width'] = metadata_json.get("width", 1216)
                    params['scale'] = metadata_json.get("scale", 6.5)
                    params['uc'] = metadata_json.get("uc", "")
                    params['sampler'] = metadata_json.get("sampler", "k_euler_ancestral")
                    params['noise_schedule'] = metadata_json.get("noise_schedule", "karras")
                    params['controlnet_strength'] = metadata_json.get("controlnet_strength", 1) == 1
                    params['prefer_brownian'] = metadata_json.get("prefer_brownian", True)
                    params['cfg_rescale'] = metadata_json.get("cfg_rescale", 0.2)
                    params['seed'] = metadata_json.get("seed", random.randint(0, 2**32 - 1))  # Default to a random seed if not found
                    params['character_name'] = extract_character_from_prompt(params['prompt'])
    except (json.JSONDecodeError, KeyError):
        print("Error parsing metadata JSON.")
    except Exception as e:
        print(f"Error reading image metadata: {e}")
    return params

# Extract character from the beginning of the positive prompt
def extract_character_from_prompt(prompt):
    return prompt.split(",")[0].strip() if prompt else ""

# Function to get random characters
def get_random_characters(count):
    characters = load_json_file(CHARACTERS_PATH)
    return ', '.join(random.sample([char['character_name'] for char in characters], count))

# Function to get random artists
def get_random_artists(count):
    artists = load_json_file(ARTISTS_PATH)
    return ', '.join(random.sample([artist['artist_name'] for artist in artists], count))

# Function to parse and replace placeholders in the prompt
def parse_prompt(prompt):
    # Replace randChar(n) with n random characters
    prompt = re.sub(r'randChar\((\d+)\)', lambda m: get_random_characters(int(m.group(1))), prompt)
    # Replace randArtist(n) with n random artists
    prompt = re.sub(r'randArtist\((\d+)\)', lambda m: get_random_artists(int(m.group(1))), prompt)
    return prompt

# Generate image based on prompt and metadata parameters
async def generate_image(api, metadata_params):
    parameters = {
        "negative_prompt": metadata_params.get("uc", ""),
        "height": metadata_params.get("height", 832),
        "width": metadata_params.get("width", 1216),
        "scale": metadata_params.get("scale", 6.5),
        "steps": metadata_params.get("steps", 28),
        "sampler": metadata_params.get("sampler", "k_euler_ancestral"),
        "noise_schedule": metadata_params.get("noise_schedule", "karras"),
        "controlnet_strength": 1.0 if metadata_params.get("controlnet_strength", True) else 0.0,
        "prefer_brownian": metadata_params.get("prefer_brownian", True),
        "cfg_rescale": metadata_params.get("cfg_rescale", 0.2),
        "seed": metadata_params.get("seed", random.randint(0, 2**32 - 1)),
    }

    prompt = metadata_params.get("prompt") or "default prompt"  # Set a default prompt if not in metadata
    prompt = parse_prompt(prompt)  # Parse the prompt to replace placeholders

    try:
        async for _, img in api.low_level.generate_image(prompt, ImageModel.Anime_v3, ImageGenerationType.NORMAL, parameters):
            return img  # Returning raw bytes
    except Exception as e:
        print(f"An error occurred during image generation: {e}")
        return None

# Gradio Interface with Blocks
with gr.Blocks(css="#col-container { max-width: 800px; }") as demo:
    characters = load_json_file(CHARACTERS_PATH)
    character_options = ['random'] + [char['character_name'] for char in characters]
    
    with gr.Column(elem_id="col-container"):
        gr.Markdown("<div id='title'>NovelAI Image Generator</div>")

        character_selection = gr.Dropdown(character_options, label="Enter or Select Character", value="random")
        additional_tags = gr.Textbox(label="Additional Tags (comma-separated)", placeholder="e.g., nsfw, vibrant colors")
        artists = gr.Textbox(label="Artists (comma-separated) or leave empty for random", placeholder="e.g., artist1, artist2")
        quality_tags = gr.Checkbox(label="Include Quality Tags", value=True)
        negative_prompt = gr.Textbox(label="Negative Prompt", value="{{worst quality, bad quality, censored}}, amputee, deformed")

        with gr.Accordion("Advanced Options", open=False):
            width = gr.Slider(512, 1216, step=64, label="Width", value=832)
            height = gr.Slider(512, 1216, step=64, label="Height", value=1216)
            scale = gr.Slider(1, 100, step=0.01, label="Scale", value=6.5)
            steps = gr.Slider(1, 50, step=1, label="Steps", value=28)
            sampler = gr.Dropdown(["k_euler", "k_euler_ancestral", "ddim", "k_dpm_2"], label="Sampler", value="k_euler_ancestral")
            noise_schedule = gr.Dropdown(["karras", "polyexponential"], label="Noise Schedule", value="karras")
            controlnet_strength = gr.Checkbox(label="ControlNet Strength", value=True)
            prefer_brownian = gr.Checkbox(label="Prefer Brownian Noise", value=True)
            cfg_rescale = gr.Slider(0, 1, step=0.01, label="CFG Rescale", value=0.2)

        metadata_upload = gr.Image(label="Upload Image with Metadata", type="filepath")

        # Function to handle image upload and extract metadata
        def update_parameters_with_metadata(image_path):
            parsed_params = parse_image_metadata(image_path)
            character_name = parsed_params.get('character_name', 'random')  # Extracted character
            return (
                character_name,
                parsed_params.get('uc', ''),
                parsed_params.get('width', 832),
                parsed_params.get('height', 1216),
                parsed_params.get('scale', 6.5),
                parsed_params.get('steps', 28),
                parsed_params.get('sampler', 'k_euler_ancestral'),
                parsed_params.get('noise_schedule', 'karras'),
                parsed_params.get('controlnet_strength', True),
                parsed_params.get('prefer_brownian', True),
                parsed_params.get('cfg_rescale', 0.2)
            )

        metadata_upload.change(
            fn=update_parameters_with_metadata,
            inputs=metadata_upload,
            outputs=[character_selection, negative_prompt, width, height, scale, steps, sampler, noise_schedule, controlnet_strength, prefer_brownian, cfg_rescale]
        )

        generate_button = gr.Button("Generate Image")
        result_image = gr.Image(type="numpy", label="Generated Image")
        result_message = gr.Textbox(label="Status")

    generate_button.click(
        fn=lambda *args: asyncio.run(generate_image(*args)),
        inputs=[
            character_selection, additional_tags, artists, quality_tags, negative_prompt,
            width, height, scale, steps, sampler, noise_schedule, controlnet_strength,
            prefer_brownian, cfg_rescale
        ],
        outputs=[result_image, result_message],
    )

if __name__ == "__main__":
    demo.launch()