File size: 16,170 Bytes
0adeb3f
 
 
1762088
632f5c5
1762088
 
 
0adeb3f
632f5c5
1762088
632f5c5
0adeb3f
 
1762088
 
 
 
 
 
 
 
 
 
 
0adeb3f
1762088
 
0adeb3f
1762088
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59ec49b
1762088
 
 
 
 
0adeb3f
1762088
59ec49b
1762088
 
 
 
 
 
 
 
0adeb3f
1762088
136d95f
1762088
 
 
 
 
 
0adeb3f
1762088
 
 
0adeb3f
1762088
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0adeb3f
 
59ec49b
1762088
 
 
 
59ec49b
 
632f5c5
1762088
 
 
 
 
0adeb3f
 
 
 
1762088
 
0adeb3f
632f5c5
0adeb3f
 
 
 
59ec49b
1762088
0adeb3f
 
 
 
632f5c5
 
0adeb3f
 
 
632f5c5
0adeb3f
632f5c5
1762088
0adeb3f
 
 
 
 
 
632f5c5
1762088
 
0adeb3f
 
 
 
632f5c5
0adeb3f
 
632f5c5
 
0adeb3f
 
 
1762088
0adeb3f
 
 
1762088
 
 
 
0adeb3f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1762088
 
0adeb3f
 
1762088
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# app.py
import gradio as gr
import torch
import requests
from PIL import Image
import numpy as np
import os
from tqdm import tqdm

# Импорты для FLUX ControlNet пайплайна
# Возможно, потребуются дополнительные импорты компонентов FLUX, если from_single_file не сработает
from diffusers import FluxControlNetPipeline, ControlNetModel, FluxPipeline
# from diffusers.utils import load_image # Не нужен для этого кода

# --- Вспомогательная функция для скачивания файлов ---
def download_file(url, local_filename):
    """Скачивает файл по URL с индикатором прогресса."""
    print(f"Скачиваю {url} в {local_filename}...")
    if os.path.exists(local_filename):
        print(f"Файл уже существует: {local_filename}. Пропускаю скачивание.")
        return local_filename

    try:
        response = requests.get(url, stream=True)
        response.raise_for_status()

        total_size_in_bytes = int(response.headers.get('content-length', 0))
        block_size = 8192

        if total_size_in_bytes > 0:
             progress_bar = tqdm(total=total_size_in_bytes, unit='iB', unit_scale=True, desc=f"Скачивание {local_filename}")
        else:
             print("Размер файла неизвестен, скачивание без индикатора прогресса.")
             progress_bar = None

        with open(local_filename, 'wb') as f:
            for chunk in response.iter_content(chunk_size=block_size):
                if progress_bar:
                    progress_bar.update(len(chunk))
                f.write(chunk)

        if progress_bar:
            progress_bar.close()

        print(f"Скачивание завершено: {local_filename}")
        return local_filename
    except requests.exceptions.RequestException as e:
        print(f"Ошибка скачивания {url}: {e}")
        return None
    except Exception as e:
        print(f"Произошла другая ошибка при скачивании: {e}")
        return None

# --- Определение путей/ID моделей ---
# URL SafeTensor модели "Flux Fusion V2" с Civitai (FP8)
CIVITAI_FLUX_FUSION_URL = "https://civitai.com/api/download/models/936565?type=Model&format=SafeTensor&fp=fp8"
# Локальное имя файла для сохранения SafeTensor модели
LOCAL_FLUX_FUSION_FILENAME = "flux_fusion_v2_fp8.safetensors"

# ControlNet модель для FLUX с Hugging Face
CONTROLNET_FLUX_MODEL_ID = "ABDALLALSWAITI/FLUX.1-dev-ControlNet-Union-Pro-2.0-fp8"

# Переменная для хранения пайплайна
pipeline = None
downloaded_base_model_path = None

# --- Скачиваем SafeTensor модель с Civitai ---
print("Начинаю скачивание базовой модели с Civitai...")
downloaded_base_model_path = download_file(CIVITAI_FLUX_FUSION_URL, LOCAL_FLUX_FUSION_FILENAME)

# --- Загрузка моделей и создание пайплайна ---
# Эта функция вызывается один раз при запуске скрипта
def load_pipeline_components(base_model_path, controlnet_model_id):
    """
    Загружает ControlNet с HF и пытается собрать пайплайн FLUX,
    используя локальный SafeTensor как базовую модель.
    """
    if not base_model_path or not os.path.exists(base_model_path):
        print(f"Ошибка загрузки: Файл базовой модели не найден по пути: {base_model_path}")
        return None

    print(f"Загрузка ControlNet модели FLUX с HF Hub: {controlnet_model_id}")
    try:
        # Загрузка ControlNet для FLUX
        controlnet = ControlNetModel.from_pretrained(controlnet_model_id, torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32)
    except Exception as e:
        print(f"Ошибка загрузки ControlNet модели с HF Hub: {controlnet_model_id}. Проверьте ID или соединение.")
        print(f"Ошибка: {e}")
        return None

    print(f"Попытка собрать пайплайн FLUX ControlNet, используя локальный файл: {base_model_path} как базовую модель.")
    print("ВНИМАНИЕ: Загрузка FLUX пайплайна из одиночного SafeTensor файла методом from_single_file")
    print("не является стандартной и может вызвать ошибки совместимости.")

    try:
        # !!! ЭТО САМАЯ ПРОБЛЕМНАЯ ЧАСТЬ !!!
        # from_single_file разработан для SD. Попытка использовать его для FLUX SafeTensor может не сработать.
        # from_pretrained для FluxControlNetPipeline ожидает ID репозитория HF или локальную ПАПКУ.
        # Здесь мы пытаемся передать локальный *файл*. Это нестандартно.
        # Возможно, придется явно указывать тип модели или компоненты, если from_single_file не сработает.
        # Например: FluxPipeline.from_single_file() если такой метод есть и работает для FLUX.
        # Или даже собрать вручную: FluxPipeline(transformer=..., vae=..., ...).from_single_file(...)
        # Попробуем передать файл в from_pretrained, хотя он обычно ждет папку/ID.
        # Или попытаемся использовать from_single_file, хотя он для SD.
        # Основываясь на предыдущем опыте, from_single_file "пытается" понять структуру.
        # Давайте попробуем from_single_file, но с большим сомнением в успехе для FLUX.

        # Попытка 1: from_single_file (наиболее вероятный источник ошибок для FLUX SafeTensor)
        # УКАЗЫВАЕМ ЯВНО controlnet=None при загрузке БАЗОВОГО пайплайна из файла
        # ControlNetModel передадим позже при создании FluxControlNetPipeline
        base_pipe = FluxPipeline.from_single_file(
             base_model_path,
             torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
             # Возможно, придется передавать явно другие компоненты, если они не в файле
             # controlnet=None # from_single_file не принимает controlnet
        )
        print("Успешно загружен базовый FLUX пайплайн из SafeTensor файла методом from_single_file (если это сообщение видно).")


        # --- Создание финального пайплайна FluxControlNetPipeline из компонентов ---
        # Собираем пайплайн, используя компоненты из базового пайплайна и ControlNet
        print("Собираю финальный FluxControlNetPipeline...")
        # Нужно убедиться, что у base_pipe есть все необходимые для FLUX компоненты (transformer, vae, etc.)
        # from_single_file мог загрузить только часть
        try:
            controlnet_pipe = FluxControlNetPipeline(
                transformer=base_pipe.transformer, # Основной компонент FLUX
                vae=base_pipe.vae,
                text_encoder=base_pipe.text_encoder, # У FLUX есть text_encoder, но другой, не как у SD CLIP
                tokenizer=base_pipe.tokenizer,
                scheduler=base_pipe.scheduler,
                controlnet=controlnet, # Передаем загруженный FLUX ControlNet
                feature_extractor=base_pipe.feature_extractor if hasattr(base_pipe, 'feature_extractor') else None, # Копируем feature_extractor
                image_processor=base_pipe.image_processor if hasattr(base_pipe, 'image_processor') else None, # Копируем image_processor
            )

            # Планировщик должен быть FLUX-совместимым, from_single_file или from_pretrained должны его загрузить.
            print(f"Финальный планировщик: {type(controlnet_pipe.scheduler).__name__}")

            # Удаляем старый объект пайплайна для освобождения памяти GPU
            del base_pipe
            if torch.cuda.is_available():
                 torch.cuda.empty_cache()
                 print("Память GPU очищена после создания ControlNet пайплайна.")

            # Перемещаем пайплайн на GPU
            if torch.cuda.is_available():
                controlnet_pipe = controlnet_pipe.to("cuda")
                print("Финальный пайплайн FLUX ControlNet перемещен на GPU.")
            else:
                 print("GPU не найдено. Пайплайн на CPU.")

            print("Сборка финального пайплайна FLUX ControlNet завершена успешно.")
            return controlnet_pipe

        except Exception as e:
            print(f"Ошибка при сборке финального FluxControlNetPipeline: {e}")
            print("Проверьте, что базовая модель, загруженная из SafeTensor, содержит все компоненты FLUX (transformer, vae, text_encoder и т.д.).")
            print("Возможно, from_single_file не смог загрузить все необходимые компоненты FLUX из этого файла.")
            return None


    except Exception as e:
         print(f"Критическая ошибка при попытке загрузить базовый FLUX пайплайн из файла {base_model_path}: {e}")
         print("Наиболее вероятно, этот файл SafeTensor несовместим с методами загрузки FLUX в diffusers.")
         print("Возможно, файл поврежден или не содержит ожидаемой структуры FLUX.")
         return None


# --- Загружаем пайплайн при запуске скрипта ---
if downloaded_base_model_path and os.path.exists(downloaded_base_model_path):
    pipeline = load_pipeline_components(downloaded_base_model_path, CONTROLNET_FLUX_MODEL_ID)
else:
    print("Пропуск загрузки пайплайна из-за ошибки скачивания или отсутствия файла.")
    pipeline = None


# --- Функция рендеринга для Gradio ---
# Эта функция будет вызываться интерфейсом Gradio в Space
# Параметры могут потребовать настройки для конкретной модели FLUX Fusion
def generate_image_gradio(controlnet_image: np.ndarray, prompt: str, negative_prompt: str = "", guidance_scale: float = 5.0, num_inference_steps: int = 4, controlnet_conditioning_scale: float = 1.0): # Значения по умолчанию подстроены под Flux Fusion
    """
    Генерирует изображение с использованием FLUX ControlNet.
    Принимает изображение NumPy, текст промта и другие параметры.
    Возвращает сгенерированное изображение в формате PIL Image.
    """
    if pipeline is None:
         print("Попытка генерации, но пайплайн модели не загружен.")
         return None, "Ошибка: Пайплайн модели не загружен. Проверьте логи Space."

    if controlnet_image is None:
        return None, "Ошибка: необходимо загрузить изображение для ControlNet."

    print(f"Генерация изображения FLUX с промтом: '{prompt}'")
    print(f"Размер входного изображения для ControlNet: {controlnet_image.shape}")

    input_image_pil = Image.fromarray(controlnet_image).convert("RGB")

    # Выполняем рендеринг с помощью пайплайна FLUX ControlNet
    try:
        # Вызов пайплайна FLUX ControlNet
        # Проверьте документацию diffusers для FluxControlNetPipeline для точных параметров вызова
        output = pipeline(
            prompt=prompt,
            image=input_image_pil, # Входное изображение для ControlNet
            negative_prompt=negative_prompt,
            guidance_scale=guidance_scale,
            num_inference_steps=num_inference_steps,
            controlnet_conditioning_scale=controlnet_conditioning_scale,
            # Для FLUX Fusion [4 steps], количество шагов (num_inference_steps) очень низкое!
            # Возможно, нужно использовать фиксированное значение 4, несмотря на ползунок?
        )

        generated_image_pil = output.images[0]

        print("Генерация FLUX завершена.")
        return generated_image_pil, "Успех!"
    except Exception as e:
        print(f"Ошибка при генерации FLUX: {e}")
        return None, f"Ошибка при генерации FLUX: {e}"


# --- Настройка интерфейса Gradio ---
# Параметры по умолчанию подстроены под Flux Fusion [4 steps]
input_image_comp = gr.Image(type="numpy", label="Изображение для ControlNet (набросок, карта глубины и т.д.)")
prompt_comp = gr.Textbox(label="Промт (Prompt)")
negative_prompt_comp = gr.Textbox(label="Негативный промт (Negative Prompt)")
# Guidance Scale для FLUX Fusion может быть ниже, чем для SD
guidance_scale_comp = gr.Slider(minimum=0.0, maximum=10.0, value=5.0, step=0.1, label="Степень соответствия промту (Guidance Scale)")
# Количество шагов для FLUX Fusion [4 steps] ОЧЕНЬ низкое
num_inference_steps_comp = gr.Slider(minimum=1, maximum=20, value=4, step=1, label="Количество шагов (Inference Steps) [для FLUX Fusion V2 обычно 4]")
controlnet_conditioning_scale_comp = gr.Slider(minimum=0.0, maximum=2.0, value=1.0, step=0.05, label="Вес ControlNet (ControlNet Scale)")

output_image_comp = gr.Image(type="pil", label="Сгенерированное изображение")
status_text_comp = gr.Textbox(label="Статус")


# Создаем интерфейс Gradio
interface = gr.Interface(
    fn=generate_image_gradio,
    inputs=[
        input_image_comp,
        prompt_comp,
        negative_prompt_comp,
        guidance_scale_comp,
        num_inference_steps_comp,
        controlnet_conditioning_scale_comp
    ],
    outputs=[output_image_comp, status_text_comp],
    title="FLUX ControlNet Interface (Attempt with Civitai SafeTensor)",
    description="Загрузите изображение для ControlNet, введите промт и нажмите 'Generate'. Попытка использовать SafeTensor 'Flux Fusion V2' с Civitai как базовую модель FLUX с ControlNet с HF."
)

# Запуск в Space обрабатывается SDK.