Spaces:
Sleeping
Sleeping
File size: 16,916 Bytes
7931060 72f2543 aaf6a9f 72f2543 aa29c52 e7d12ee 7931060 72f2543 7931060 72f2543 7931060 e7d12ee 72f2543 7931060 e7d12ee aaf6a9f 72f2543 e7d12ee 36a3e6b 7931060 72f2543 36a3e6b 72f2543 36a3e6b 72f2543 36a3e6b e7d12ee 72f2543 36a3e6b 72f2543 e7d12ee 72f2543 e7d12ee 36a3e6b e7d12ee 72f2543 e7d12ee 72f2543 e7d12ee 36a3e6b 72f2543 e7d12ee 72f2543 36a3e6b e7d12ee 72f2543 e7d12ee 72f2543 e7d12ee 72f2543 b5409e4 72f2543 b5409e4 e7d12ee 72f2543 e7d12ee 72f2543 e7d12ee 72f2543 e7d12ee 72f2543 e7d12ee 72f2543 e7d12ee 72f2543 e7d12ee 72f2543 e7d12ee 72f2543 e7d12ee 72f2543 e7d12ee 72f2543 e7d12ee 72f2543 e7d12ee 72f2543 e7d12ee 36a3e6b 72f2543 e7d12ee 72f2543 7931060 e7d12ee 72f2543 e7d12ee 72f2543 e7d12ee 72f2543 e7d12ee 72f2543 e7d12ee 72f2543 7931060 72f2543 7931060 72f2543 7931060 72f2543 7931060 72f2543 7931060 72f2543 7931060 72f2543 7931060 72f2543 7931060 72f2543 7931060 72f2543 7931060 72f2543 7931060 72f2543 7931060 72f2543 7931060 72f2543 |
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 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
import gradio as gr
# requests jest nadal w requirements.txt, ale nie jest już bezpośrednio używany do głównego wywołania API
import requests
from PIL import Image, ImageOps
import io
import os
import traceback
# Poprawny import dla gradio_client
from gradio_client import Client, file
import uuid
import shutil
print("--- Plik app.py - Start ładowania ---")
# --- Konfiguracja ---
# Odczytanie sekretu (API Token) ustawionego w Space
API_TOKEN = os.getenv("HUGGINGFACE_API_TOKEN")
# Sprawdzenie, czy token został załadowany
if not API_TOKEN:
# Nadal ostrzegamy, ale klient może działać anonimowo
print("!!! OSTRZEŻENIE: Nie znaleziono sekretu HUGGINGFACE_API_TOKEN. Klient Gradio spróbuje połączyć się anonimowo (mogą obowiązywać limity). !!!")
else:
print("--- Sekret HUGGINGFACE_API_TOKEN załadowany. ---")
# Profesjonalny prompt, który chcemy uzyskać
LINKEDIN_PROMPT = (
"linkedin professional profile photo, corporate headshot, high quality, realistic photograph, "
"person wearing a dark business suit or elegant blouse, plain white background, " # Usunięto potencjalnie problematyczne tagi
"soft studio lighting, sharp focus, looking at camera, slight smile, natural skin texture"
)
# Adres publicznego Space'a z InstantID
# Używamy identyfikatora Space'a dla gradio_client
TARGET_SPACE_ID = "InstantX/InstantID"
# Kontrolny print, aby sprawdzić, czy zmienna jest ładowana na starcie
# Powinien pojawić się w logach kontenera po restarcie Space'a
print(f"--- Konfiguracja załadowana. Cel dla gradio_client: {TARGET_SPACE_ID} ---")
# --- Logika aplikacji ---
def generate_photo(input_selfie, current_prompt):
print(f"Otrzymano obrazek typu: {type(input_selfie)}, Oryginalny rozmiar: {input_selfie.size}")
print(f"Otrzymano prompt (początek): {current_prompt[:100]}...")
# --- Dodany Krok: Przeskalowanie obrazka wejściowego ---
try:
max_size = (1024, 1024) # Maksymalny rozmiar (szerokość, wysokość)
# Używamy thumbnail, aby zachować proporcje i zmniejszyć tylko jeśli jest większy
input_selfie_resized = input_selfie.copy() # Pracuj na kopii
input_selfie_resized.thumbnail(max_size, Image.Resampling.LANCZOS)
print(f"Obrazek przeskalowany do (maksymalnie) {max_size}. Nowy rozmiar: {input_selfie_resized.size}")
except Exception as e_resize:
print(f"BŁĄD podczas skalowania obrazka: {e_resize}")
traceback.print_exc()
raise gr.Error(f"Nie można przeskalować obrazka wejściowego: {e_resize}")
# ---------------------------------------------------------
# 2. Przygotowanie pliku tymczasowego dla obrazka wejściowego
# (Używamy teraz przeskalowanego obrazka: input_selfie_resized)
temp_dir = f"temp_input_{uuid.uuid4()}"
input_image_path = None
try:
os.makedirs(temp_dir, exist_ok=True)
input_image_path = os.path.join(temp_dir, "input_selfie.jpg")
# Upewnijmy się, że obraz jest w RGB przed zapisem jako JPEG
rgb_image = input_selfie_resized # Używamy przeskalowanego!
if rgb_image.mode == 'RGBA':
print("Konwertuję przeskalowany obraz RGBA na RGB z białym tłem...")
# Stwórz nowy obraz RGB wypełniony białym kolorem
final_image = Image.new("RGB", rgb_image.size, (255, 255, 255))
# Wklej oryginalny obraz używając jego kanału alfa jako maski
final_image.paste(rgb_image, mask=rgb_image.split()[3])
else:
final_image = rgb_image # Już jest w odpowiednim trybie
# Zapisz przeskalowany i skonwertowany obraz jako JPEG
final_image.save(input_image_path, format="JPEG")
print(f"Przeskalowany obrazek wejściowy zapisany tymczasowo w: {input_image_path}")
except Exception as e:
# ... (reszta obsługi błędu zapisu bez zmian) ...
# 3. Wywołanie zdalnego API Gradio za pomocą gradio_client
# ... (reszta kodu bez zmian, aż do obsługi błędu AppError) ...
except Exception as e:
print(f"BŁĄD podczas komunikacji z klientem Gradio lub przetwarzania wyniku: {e}")
traceback.print_exc()
# Dodajmy podpowiedź o możliwej przyczynie błędu AppError
error_message = f"Problem podczas generowania przez zdalny serwis InstantID: {e}"
if isinstance(e, AppError): # Sprawdzamy czy to ten konkretny błąd
error_message = f"Zdalny serwis ({TARGET_SPACE_ID}) zgłosił wewnętrzny błąd. Najczęstszą przyczyną są problemy z obrazkiem wejściowym (np. brak wykrytej twarzy, nietypowy format) lub przeciążenie serwisu. Spróbuj z innym zdjęciem lub ponownie później."
elif "Could not fetch config" in str(e):
# ... (reszta obsługi błędów bez zmian)
raise gr.Error(error_message) # Pokaż (potencjalnie bardziej pomocny) błąd
# Ta funkcja jest teraz wywoływana przez Gradio po kliknięciu przycisku
print("\n--- Funkcja generate_photo (gradio_client) została wywołana ---")
# 1. Walidacja danych wejściowych
if input_selfie is None:
print("BŁĄD: Nie wgrano zdjęcia wejściowego.")
raise gr.Error("Proszę najpierw wgrać swoje selfie!") # Pokaż błąd w interfejsie
if not current_prompt:
print("BŁĄD: Prompt jest pusty.")
raise gr.Error("Prompt (opis zdjęcia) nie może być pusty!")
# Informacja o tokenie (nie jest już krytyczny dla działania, ale może wpływać na limity)
if not API_TOKEN:
print("INFO: Brak API Tokena. Połączenie z publicznym Space jako anonimowy użytkownik.")
print(f"Otrzymano obrazek typu: {type(input_selfie)}, Rozmiar: {input_selfie.size}")
print(f"Otrzymano prompt (początek): {current_prompt[:100]}...")
# 2. Przygotowanie pliku tymczasowego dla obrazka wejściowego
temp_dir = f"temp_input_{uuid.uuid4()}" # Unikalny folder tymczasowy
input_image_path = None # Inicjalizacja na wypadek błędu przed przypisaniem
try:
os.makedirs(temp_dir, exist_ok=True)
# Pełna ścieżka do pliku tymczasowego
input_image_path = os.path.join(temp_dir, "input_selfie.jpg")
# Upewnijmy się, że obraz jest w RGB przed zapisem jako JPEG
# To ważne, bo format JPEG nie obsługuje przezroczystości (kanału alfa)
rgb_image = input_selfie
if input_selfie.mode == 'RGBA':
print("Konwertuję obraz RGBA na RGB z białym tłem przed zapisem...")
# Stwórz nowy obraz RGB wypełniony białym kolorem
rgb_image = Image.new("RGB", input_selfie.size, (255, 255, 255))
# Wklej oryginalny obraz używając jego kanału alfa jako maski
rgb_image.paste(input_selfie, mask=input_selfie.split()[3])
# Zapisz przetworzony obraz jako JPEG
rgb_image.save(input_image_path, format="JPEG")
print(f"Obrazek wejściowy zapisany tymczasowo w: {input_image_path}")
except Exception as e:
print(f"BŁĄD podczas zapisywania obrazu tymczasowego: {e}")
traceback.print_exc() # Pokaż pełny błąd w logach
# Spróbuj posprzątać folder tymczasowy nawet jeśli zapis się nie udał
if temp_dir and os.path.exists(temp_dir):
try:
shutil.rmtree(temp_dir)
print(f"Folder tymczasowy {temp_dir} usunięty po błędzie zapisu.")
except Exception as e_clean:
print(f"OSTRZEŻENIE: Nie udało się usunąć folderu tymczasowego {temp_dir} po błędzie zapisu: {e_clean}")
# Pokaż błąd użytkownikowi
raise gr.Error(f"Problem z przygotowaniem obrazu do wysłania: {e}")
# 3. Wywołanie zdalnego API Gradio za pomocą gradio_client
output_image = None # Zmienna na wynikowy obrazek
client = None # Zmienna na obiekt klienta
try:
# Używamy TARGET_SPACE_ID zdefiniowanej globalnie
print(f"Łączenie z docelowym Space Gradio: {TARGET_SPACE_ID}")
# Inicjalizujemy klienta, przekazując ID Space'a i token (jeśli jest)
client = Client(TARGET_SPACE_ID, hf_token=API_TOKEN)
print("Połączono. Próbuję wywołać funkcję na zdalnym Space...")
# --- Konfiguracja parametrów dla InstantX/InstantID ---
# Te wartości mogą wymagać dostosowania w zależności od dokładnej
# konfiguracji zdalnego Space'a i pożądanych efektów.
negative_prompt = "ugly, deformed, noisy, blurry, low contrast, text, signature, watermark, duplicate, multiple people, cartoon, drawing, illustration, sketch"
# Popularne style w demo InstantID: Realistic, (No style), Comic book, Disney, Pixar
style_name = "Realistic"
# Skale ControlNet i IP-Adapter (siła tożsamości) - typowe wartości to 0.6-1.0
cn_scale = 0.8
ip_scale = 0.8
# Nazwa endpointu API w zdalnym Space'u (często '/predict' lub specyficzna jak '/generate_image')
# Dla InstantX/InstantID wydaje się, że to '/generate_image'
api_endpoint_name = "/generate_image"
# -------------------------------------------------------
print(f"Wywołuję endpoint '{api_endpoint_name}' z parametrami:")
print(f" Input image path: {input_image_path}")
# Nie drukujemy całego promptu, bo może być długi
print(f" Prompt (start): {current_prompt[:60]}...")
print(f" Negative Prompt (start): {negative_prompt[:60]}...")
print(f" Style: {style_name}")
print(f" ControlNet Scale: {cn_scale}")
print(f" IP-Adapter Scale: {ip_scale}")
# Wywołanie funkcji 'predict' na zdalnym kliencie
result = client.predict(
file(input_image_path), # Obraz twarzy (jako obiekt pliku Gradio)
None, # Obraz pozy (opcjonalny, dajemy None)
current_prompt, # Prompt tekstowy
negative_prompt, # Negatywny prompt
style_name, # Nazwa stylu
cn_scale, # Skala ControlNet
ip_scale, # Skala IP-Adapter (siła tożsamości)
api_name=api_endpoint_name # Nazwa endpointu API
)
# Przetwarzanie wyniku zwróconego przez klienta
print(f"Otrzymano wynik od klienta: {type(result)}")
# Wydrukuj fragment wyniku, aby zobaczyć jego strukturę
print(f"Wynik (fragment): {str(result)[:500]}")
# Sprawdzamy, czy wynik jest listą ścieżek do plików (typowe dla Gradio)
if isinstance(result, list) and len(result) > 0 and isinstance(result[0], str) and os.path.exists(result[0]):
output_file_path = result[0] # Bierzemy pierwszą ścieżkę z listy
print(f"Przetwarzam pierwszy obrazek wynikowy ze ścieżki: {output_file_path}")
# Wczytaj obrazek wynikowy z tej ścieżki za pomocą Pillow
output_image = Image.open(output_file_path)
print(f"Obrazek wynikowy załadowany pomyślnie. Rozmiar: {output_image.size}")
# Sprawdzamy, czy wynik jest pojedynczą ścieżką do pliku
elif isinstance(result, str) and os.path.exists(result):
output_file_path = result
print(f"Przetwarzam obrazek wynikowy ze ścieżki: {output_file_path}")
output_image = Image.open(output_file_path)
print(f"Obrazek wynikowy załadowany pomyślnie. Rozmiar: {output_image.size}")
else:
# Jeśli wynik nie jest ani listą ścieżek, ani pojedynczą ścieżką
print(f"BŁĄD: Otrzymano nieoczekiwany format wyniku od gradio_client: {type(result)}")
raise gr.Error(f"Nie udało się przetworzyć wyniku ze zdalnego API. Otrzymano: {str(result)[:200]}")
except Exception as e:
# Obsługa wszelkich błędów podczas komunikacji z klientem lub przetwarzania wyniku
print(f"BŁĄD podczas komunikacji z klientem Gradio lub przetwarzania wyniku: {e}")
traceback.print_exc() # Pokaż pełny błąd w logach
# Spróbuj dać użytkownikowi bardziej pomocny komunikat
error_message = f"Problem podczas generowania przez zdalny serwis InstantID: {e}"
if "Could not fetch config" in str(e):
error_message = f"Nie można połączyć się z konfiguracją zdalnego serwisu ({TARGET_SPACE_ID}). Może być chwilowo niedostępny, przeciążony lub wymagać logowania. Spróbuj ponownie później."
elif "timed out" in str(e).lower():
error_message = "Przekroczono limit czasu oczekiwania na odpowiedź ze zdalnego serwisu. Może być przeciążony. Spróbuj ponownie."
elif "queue full" in str(e).lower():
error_message = "Kolejka w zdalnym serwisie jest pełna. Spróbuj ponownie za chwilę."
# Pokaż błąd w interfejsie Gradio
raise gr.Error(error_message)
finally:
# 4. Sprzątanie - ZAWSZE próbuj usunąć folder tymczasowy, nawet jeśli był błąd
if temp_dir and os.path.exists(temp_dir):
try:
shutil.rmtree(temp_dir)
print(f"Folder tymczasowy {temp_dir} usunięty.")
except Exception as e_clean:
# Tylko ostrzeżenie, jeśli sprzątanie się nie uda
print(f"OSTRZEŻENIE: Nie udało się usunąć folderu tymczasowego {temp_dir}: {e_clean}")
# 5. Zwróć wynik (załadowany obrazek PIL), jeśli się udało
if output_image:
print("Zwracam wygenerowany obrazek do interfejsu Gradio.")
return output_image
else:
# Ten kod nie powinien zostać osiągnięty, jeśli błędy są poprawnie obsługiwane
print("BŁĄD KRYTYCZNY: Brak obrazka wynikowego po zakończeniu funkcji, a nie zgłoszono błędu.")
raise gr.Error("Nie udało się uzyskać obrazka wynikowego z nieznanego powodu.")
# --- Budowa Interfejsu Gradio ---
print("--- Definiowanie interfejsu Gradio ---")
# Tworzymy główny blok aplikacji Gradio
with gr.Blocks(css="footer {display: none !important}") as demo: # Ukrywamy domyślny footer Gradio
# Tytuł i opis aplikacji wyświetlany użytkownikowi
gr.Markdown(
"""
# Generator Profesjonalnych Zdjęć Profilowych
Wgraj swoje selfie, a my (korzystając z modelu InstantID) postaramy się stworzyć profesjonalne zdjęcie w stylu LinkedIn!
**Wskazówki:**
* Użyj wyraźnego zdjęcia twarzy, patrzącej w miarę prosto, dobrze oświetlonej.
* Unikaj zdjęć grupowych, bardzo małych lub z mocno zasłoniętą twarzą.
* Generowanie może potrwać od 30 sekund do kilku minut, bądź cierpliwy!
"""
)
# Układ interfejsu w dwóch kolumnach
with gr.Row():
# Kolumna lewa (wejście)
with gr.Column(scale=1):
# Komponent do wgrywania obrazka
input_image = gr.Image(
label="1. Wgraj swoje selfie (JPG/PNG)",
type="pil" # Chcemy obiekt PIL w funkcji Pythona
)
# Pole tekstowe do wpisania promptu (opisu zdjęcia)
prompt_input = gr.Textbox(
label="2. Opis pożądanego zdjęcia (prompt)",
value=LINKEDIN_PROMPT, # Ustawiamy domyślny prompt
lines=4 # Określamy wysokość pola
)
# Przycisk uruchamiający generowanie
generate_button = gr.Button("✨ Generuj Zdjęcie Biznesowe ✨", variant="primary") # Wyróżniony przycisk
# Kolumna prawa (wyjście)
with gr.Column(scale=1):
# Komponent do wyświetlania wyniku (obrazka)
output_image = gr.Image(
label="Oto Twoje wygenerowane zdjęcie:",
type="pil" # Oczekujemy obiektu PIL jako wynik
)
# --- Podłączenie akcji do przycisku ---
# Definiujemy, co ma się stać po kliknięciu przycisku 'generate_button'
generate_button.click(
fn=generate_photo, # Wywołaj funkcję 'generate_photo'
inputs=[input_image, prompt_input], # Przekaż zawartość 'input_image' i 'prompt_input' jako argumenty
outputs=[output_image] # Wynik funkcji umieść w komponencie 'output_image'
)
print("--- Interfejs Gradio zdefiniowany ---")
# --- Uruchomienie aplikacji ---
if __name__ == "__main__":
print("--- Uruchamianie demo.launch() ---")
# Uruchamiamy aplikację Gradio
# share=False: nie generuj publicznego linku (zalecane dla bezpieczeństwa)
# debug=False: wyłącz tryb debugowania Gradio (zalecane dla produkcji)
demo.launch(share=False, debug=False)
print("--- Aplikacja Gradio zakończyła działanie (jeśli nie jest w trybie ciągłym serwera) ---") |