Update app.py
Browse files
app.py
CHANGED
@@ -4,6 +4,9 @@ from PIL import Image # Dodajemy od razu
|
|
4 |
import io # Dodajemy od razu
|
5 |
import os # Do odczytu sekretu
|
6 |
import traceback # Do lepszego śledzenia błędów
|
|
|
|
|
|
|
7 |
|
8 |
print("--- Plik app.py - Start ładowania ---")
|
9 |
|
@@ -31,13 +34,15 @@ LINKEDIN_PROMPT = (
|
|
31 |
"man/woman [choose one or remove] wearing a dark business suit or elegant blouse, plain white background, "
|
32 |
"soft studio lighting, sharp focus, looking at camera, slight smile, natural skin texture"
|
33 |
)
|
|
|
|
|
34 |
# Uwaga: InstantID może nie być idealny w zmianie ubrań, skupia się na twarzy.
|
35 |
# Prompt pomaga nadać ogólny styl.
|
36 |
|
37 |
# --- Logika aplikacji ---
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
# 1. Walidacja danych wejściowych
|
42 |
if input_selfie is None:
|
43 |
print("Błąd: Nie wgrano zdjęcia wejściowego.")
|
@@ -45,118 +50,112 @@ def generate_photo(input_selfie, current_prompt): # Dodajemy prompt jako wejści
|
|
45 |
if not current_prompt:
|
46 |
print("Błąd: Prompt jest pusty.")
|
47 |
raise gr.Error("Prompt nie może być pusty!")
|
|
|
48 |
if not API_TOKEN:
|
49 |
-
|
50 |
-
|
51 |
|
52 |
print(f"Otrzymano obrazek typu: {type(input_selfie)}, Rozmiar: {input_selfie.size}")
|
53 |
print(f"Otrzymano prompt (początek): {current_prompt[:100]}...")
|
54 |
-
print("Przygotowuję dane do wysłania do API...")
|
55 |
|
56 |
-
# 2. Przygotowanie
|
|
|
|
|
|
|
57 |
try:
|
58 |
-
|
59 |
-
|
60 |
-
image_format = "JPEG" # Preferowany format dla wielu API
|
61 |
-
processed_image = input_selfie
|
62 |
if input_selfie.mode == 'RGBA':
|
63 |
-
print("Konwertuję obraz RGBA na RGB z białym tłem...")
|
64 |
-
|
65 |
-
|
66 |
|
67 |
-
|
68 |
-
|
69 |
-
print(f"Obrazek przekonwertowany do formatu {image_format} i zapisany w buforze.")
|
70 |
except Exception as e:
|
71 |
-
print(f"BŁĄD podczas
|
72 |
-
traceback.print_exc() # Wydrukuj pełny ślad błędu w logach
|
73 |
-
raise gr.Error(f"Wystąpił błąd podczas przetwarzania Twojego zdjęcia: {e}")
|
74 |
-
|
75 |
-
# 3. Przygotowanie nagłówków i danych dla API
|
76 |
-
headers = {
|
77 |
-
"Authorization": f"Bearer {API_TOKEN}"
|
78 |
-
# Content-Type zostanie ustawiony automatycznie przez requests dla multipart/form-data
|
79 |
-
}
|
80 |
-
|
81 |
-
# InstantID (i wiele modeli image-to-image w Inference API) oczekuje
|
82 |
-
# obrazu w 'files' i parametrów (jak prompt) w 'data'.
|
83 |
-
# WAŻNE: Nazwa klucza dla obrazu ('image') i promptu ('prompt')
|
84 |
-
# MUSI odpowiadać temu, czego oczekuje API modelu Sxela/instant-id.
|
85 |
-
# Sprawdź dokumentację modelu na HF, jeśli to nie zadziała!
|
86 |
-
files = {
|
87 |
-
'image': ('input_selfie.jpg', buffered, f'image/{image_format.lower()}')
|
88 |
-
}
|
89 |
-
payload = {
|
90 |
-
'prompt': current_prompt
|
91 |
-
# Możesz tu dodać inne parametry wspierane przez model, np.:
|
92 |
-
# 'negative_prompt': 'cartoon, drawing, illustration, sketch, duplicate heads, multiple people',
|
93 |
-
# 'num_inference_steps': 30,
|
94 |
-
# 'guidance_scale': 5.0,
|
95 |
-
}
|
96 |
-
|
97 |
-
print(f"Wysyłam zapytanie POST do: {API_URL}")
|
98 |
-
print(f"Payload (parametry tekstowe): {payload}")
|
99 |
-
# Nie drukujemy 'files', bo zawiera dane binarne
|
100 |
-
|
101 |
-
# 4. Wywołanie API i obsługa odpowiedzi
|
102 |
-
try:
|
103 |
-
# Zwiększamy timeout, bo generowanie może chwilę potrwać
|
104 |
-
response = requests.post(API_URL, headers=headers, data=payload, files=files, timeout=180) # 3 minuty timeout
|
105 |
-
|
106 |
-
print(f"Otrzymano odpowiedź od API. Status: {response.status_code}")
|
107 |
-
# Zawsze sprawdzaj content-type odpowiedzi!
|
108 |
-
content_type = response.headers.get('Content-Type', '')
|
109 |
-
print(f"Content-Type odpowiedzi: {content_type}")
|
110 |
-
|
111 |
-
# Rzuć wyjątek dla złych statusów (4xx, 5xx)
|
112 |
-
response.raise_for_status()
|
113 |
-
|
114 |
-
# Sprawdź, czy odpowiedź to na pewno obrazek
|
115 |
-
if 'image' not in content_type:
|
116 |
-
# Jeśli API zwróciło sukces (200), ale nie obrazek, to prawdopodobnie błąd w JSON
|
117 |
-
print(f"BŁĄD: API zwróciło status {response.status_code} ale nie obrazek (Content-Type: {content_type}). Treść:")
|
118 |
-
# Spróbuj wydrukować treść jako tekst (może zawierać komunikat błędu)
|
119 |
-
try:
|
120 |
-
print(response.text[:1000]) # Pokaż do 1000 znaków
|
121 |
-
raise gr.Error(f"API zwróciło nieoczekiwany format danych zamiast obrazka. Szczegóły: {response.text[:200]}")
|
122 |
-
except Exception as json_err:
|
123 |
-
print(f"Nie można odczytać treści odpowiedzi jako tekst: {json_err}")
|
124 |
-
raise gr.Error(f"API zwróciło nieoczekiwany format danych (Content-Type: {content_type}).")
|
125 |
-
|
126 |
-
# Jeśli status OK i Content-Type to obrazek, otwórz go
|
127 |
-
print("Próbuję otworzyć obrazek z odpowiedzi API...")
|
128 |
-
generated_image = Image.open(io.BytesIO(response.content))
|
129 |
-
print(f"Sukces! Otworzono wygenerowany obrazek. Rozmiar: {generated_image.size}")
|
130 |
-
|
131 |
-
# Zwróć wygenerowany obrazek do interfejsu Gradio
|
132 |
-
return generated_image # Zwracamy obiekt PIL
|
133 |
-
|
134 |
-
# Obsługa błędów z biblioteki requests (np. timeout, problem z połączeniem, błąd statusu z raise_for_status)
|
135 |
-
except requests.exceptions.RequestException as e:
|
136 |
-
print(f"BŁĄD podczas komunikacji z API: {e}")
|
137 |
-
# Spróbujmy wyświetlić odpowiedź błędu z API, jeśli jest dostępna
|
138 |
-
error_details = str(e)
|
139 |
-
if e.response is not None:
|
140 |
-
print(f"Odpowiedź błędu API (status {e.response.status_code}):")
|
141 |
-
try:
|
142 |
-
# Spróbuj zdekodować JSON jeśli to możliwe
|
143 |
-
error_json = e.response.json()
|
144 |
-
print(error_json)
|
145 |
-
error_details = f"API Error (status {e.response.status_code}): {error_json.get('error', e.response.text)}"
|
146 |
-
except ValueError: # Not JSON
|
147 |
-
error_text = e.response.text[:500] # Pokaż fragment tekstu błędu
|
148 |
-
print(error_text)
|
149 |
-
error_details = f"API Error (status {e.response.status_code}): {error_text}"
|
150 |
-
|
151 |
traceback.print_exc()
|
152 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
153 |
|
154 |
-
# Obsługa innych, nieoczekiwanych błędów
|
155 |
except Exception as e:
|
156 |
-
print(f"
|
157 |
-
traceback.print_exc()
|
158 |
-
raise gr.Error(f"
|
159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
160 |
|
161 |
# --- Budowa Interfejsu Gradio ---
|
162 |
print("--- Definiowanie interfejsu Gradio ---")
|
|
|
4 |
import io # Dodajemy od razu
|
5 |
import os # Do odczytu sekretu
|
6 |
import traceback # Do lepszego śledzenia błędów
|
7 |
+
import gradio_client import Client, file
|
8 |
+
import uuid
|
9 |
+
import shutil
|
10 |
|
11 |
print("--- Plik app.py - Start ładowania ---")
|
12 |
|
|
|
34 |
"man/woman [choose one or remove] wearing a dark business suit or elegant blouse, plain white background, "
|
35 |
"soft studio lighting, sharp focus, looking at camera, slight smile, natural skin texture"
|
36 |
)
|
37 |
+
|
38 |
+
TARGET_SPACE_URL = "https://huggingface.co/spaces/InstantX/InstantID"
|
39 |
# Uwaga: InstantID może nie być idealny w zmianie ubrań, skupia się na twarzy.
|
40 |
# Prompt pomaga nadać ogólny styl.
|
41 |
|
42 |
# --- Logika aplikacji ---
|
43 |
+
def generate_photo(input_selfie, current_prompt):
|
44 |
+
print("\n--- Funkcja generate_photo (gradio_client) została wywołana ---")
|
45 |
+
|
46 |
# 1. Walidacja danych wejściowych
|
47 |
if input_selfie is None:
|
48 |
print("Błąd: Nie wgrano zdjęcia wejściowego.")
|
|
|
50 |
if not current_prompt:
|
51 |
print("Błąd: Prompt jest pusty.")
|
52 |
raise gr.Error("Prompt nie może być pusty!")
|
53 |
+
# Sprawdźmy też token, bo może być potrzebny do połączenia z klientem
|
54 |
if not API_TOKEN:
|
55 |
+
print("OSTRZEŻENIE: Brak API Tokena. Połączenie z publicznym Space może działać, ale może być ograniczone.")
|
56 |
+
# Nie rzucamy błędu, spróbujemy połączyć się anonimowo
|
57 |
|
58 |
print(f"Otrzymano obrazek typu: {type(input_selfie)}, Rozmiar: {input_selfie.size}")
|
59 |
print(f"Otrzymano prompt (początek): {current_prompt[:100]}...")
|
|
|
60 |
|
61 |
+
# 2. Przygotowanie pliku tymczasowego dla obrazka wejściowego
|
62 |
+
temp_dir = f"temp_input_{uuid.uuid4()}" # Unikalny folder tymczasowy
|
63 |
+
os.makedirs(temp_dir, exist_ok=True)
|
64 |
+
input_image_path = os.path.join(temp_dir, "input_selfie.jpg")
|
65 |
try:
|
66 |
+
# Upewnijmy się, że obraz jest w RGB przed zapisem jako JPEG
|
67 |
+
rgb_image = input_selfie
|
|
|
|
|
68 |
if input_selfie.mode == 'RGBA':
|
69 |
+
print("Konwertuję obraz RGBA na RGB z białym tłem przed zapisem...")
|
70 |
+
rgb_image = Image.new("RGB", input_selfie.size, (255, 255, 255))
|
71 |
+
rgb_image.paste(input_selfie, mask=input_selfie.split()[3])
|
72 |
|
73 |
+
rgb_image.save(input_image_path, format="JPEG")
|
74 |
+
print(f"Obrazek wejściowy zapisany tymczasowo w: {input_image_path}")
|
|
|
75 |
except Exception as e:
|
76 |
+
print(f"BŁĄD podczas zapisywania obrazu tymczasowego: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
traceback.print_exc()
|
78 |
+
# Spróbuj posprzątać folder tymczasowy
|
79 |
+
if os.path.exists(temp_dir):
|
80 |
+
shutil.rmtree(temp_dir)
|
81 |
+
raise gr.Error(f"Problem z przygotowaniem obrazu do wysłania: {e}")
|
82 |
+
|
83 |
+
# 3. Wywołanie zdalnego API Gradio za pomocą gradio_client
|
84 |
+
output_image = None
|
85 |
+
try:
|
86 |
+
print(f"Łączenie z docelowym Space Gradio: {TARGET_SPACE_URL}")
|
87 |
+
# Inicjalizujemy klienta, przekazując token (może pomóc z limitami)
|
88 |
+
client = Client(TARGET_SPACE_URL, hf_token=API_TOKEN)
|
89 |
+
# Możesz spróbować bez tokena, jeśli są problemy: client = Client(TARGET_SPACE_URL)
|
90 |
+
|
91 |
+
print("Połączono. Próbuję wywołać funkcję predict...")
|
92 |
+
|
93 |
+
# WAŻNE: Musimy zgadnąć lub sprawdzić (np. przez client.view_api() lub patrząc na UI/API zdalnego Space'a),
|
94 |
+
# jakich dokładnie argumentów i w jakiej kolejności oczekuje funkcja 'predict' w zdalnym Space.
|
95 |
+
# Poniżej jest ZGADYWANA struktura bazująca na typowym demo InstantID. Może wymagać dostosowania!
|
96 |
+
# Argumenty: (Twarz, Poza, Prompt, NegPrompt, Styl, Skala CN, Skala IP)
|
97 |
+
# Używamy `file()` do przekazania ścieżki pliku obrazka.
|
98 |
+
|
99 |
+
negative_prompt = "ugly, deformed, noisy, blurry, low contrast, text, signature, watermark, duplicate, multiple people"
|
100 |
+
style_name = "Realistic" # Lub "(No style)", "Comic book" etc. - sprawdź opcje w docelowym Space!
|
101 |
+
|
102 |
+
result = client.predict(
|
103 |
+
file(input_image_path), # Argument 1: Obraz twarzy (jako plik)
|
104 |
+
None, # Argument 2: Obraz pozy (opcjonalny, dajemy None)
|
105 |
+
current_prompt, # Argument 3: Prompt
|
106 |
+
negative_prompt, # Argument 4: Negative prompt
|
107 |
+
style_name, # Argument 5: Nazwa stylu
|
108 |
+
0.8, # Argument 6: Skala ControlNet (zgadnięta wartość domyślna)
|
109 |
+
0.8, # Argument 7: Skala IdentityNet (IP Adapter) (zgadnięta wartość domyślna)
|
110 |
+
api_name="/generate_image" # Zwykle nazywa się '/predict', ale InstantX używa '/generate_image'
|
111 |
+
# Sprawdź API docelowego space jeśli to nie działa! Może być "/predict"
|
112 |
+
# Można sprawdzić programowo: client.view_api(all_endpoints=True) i wydrukować w logach
|
113 |
+
)
|
114 |
+
|
115 |
+
# `predict` zwraca wynik zgodny z wyjściami zdalnego API.
|
116 |
+
# Dla InstantID zwykle jest to lista obrazów (nawet jeśli jest jeden).
|
117 |
+
# Często zwraca ścieżkę do pliku (lub listę ścieżek) zapisanego tymczasowo przez gradio_client.
|
118 |
+
print(f"Otrzymano wynik od klienta: {type(result)}")
|
119 |
+
print(f"Wynik (fragment): {str(result)[:500]}") # Wydrukuj fragment wyniku
|
120 |
+
|
121 |
+
# Sprawdźmy, czy wynik to lista i czy zawiera ścieżkę do pliku
|
122 |
+
if isinstance(result, list) and len(result) > 0 and isinstance(result[0], str) and os.path.exists(result[0]):
|
123 |
+
output_file_path = result[0]
|
124 |
+
print(f"Wygląda na to, że otrzymano ścieżkę do obrazka: {output_file_path}")
|
125 |
+
# Wczytaj obrazek wynikowy z tej ścieżki
|
126 |
+
output_image = Image.open(output_file_path)
|
127 |
+
print(f"Obrazek wynikowy załadowany. Rozmiar: {output_image.size}")
|
128 |
+
elif isinstance(result, str) and os.path.exists(result): # Czasem zwraca tylko jedną ścieżkę
|
129 |
+
output_file_path = result
|
130 |
+
print(f"Wygląda na to, że otrzymano ścieżkę do obrazka: {output_file_path}")
|
131 |
+
output_image = Image.open(output_file_path)
|
132 |
+
print(f"Obrazek wynikowy załadowany. Rozmiar: {output_image.size}")
|
133 |
+
else:
|
134 |
+
# Jeśli wynik nie jest oczekiwaną ścieżką, zgłoś błąd
|
135 |
+
print(f"BŁĄD: Otrzymano nieoczekiwany format wyniku od gradio_client: {type(result)}")
|
136 |
+
raise gr.Error(f"Nie udało się przetworzyć wyniku ze zdalnego API. Otrzymano: {str(result)[:200]}")
|
137 |
|
|
|
138 |
except Exception as e:
|
139 |
+
print(f"BŁĄD podczas komunikacji z klientem Gradio lub przetwarzania wyniku: {e}")
|
140 |
+
traceback.print_exc()
|
141 |
+
raise gr.Error(f"Problem podczas generowania przez zdalny serwis InstantID: {e}")
|
142 |
|
143 |
+
finally:
|
144 |
+
# 4. Sprzątanie - usuń folder tymczasowy z plikiem wejściowym
|
145 |
+
if os.path.exists(temp_dir):
|
146 |
+
try:
|
147 |
+
shutil.rmtree(temp_dir)
|
148 |
+
print(f"Folder tymczasowy {temp_dir} usunięty.")
|
149 |
+
except Exception as e_clean:
|
150 |
+
print(f"OSTRZEŻENIE: Nie udało się usunąć folderu tymczasowego {temp_dir}: {e_clean}")
|
151 |
+
|
152 |
+
# 5. Zwróć wynik (załadowany obrazek PIL)
|
153 |
+
if output_image:
|
154 |
+
return output_image
|
155 |
+
else:
|
156 |
+
# To nie powinno się zdarzyć, jeśli nie było błędu wcześniej, ale dla pewności:
|
157 |
+
print("BŁĄD: Brak obrazka wynikowego po zakończeniu funkcji.")
|
158 |
+
raise gr.Error("Nie udało się uzyskać obrazka wynikowego.")
|
159 |
|
160 |
# --- Budowa Interfejsu Gradio ---
|
161 |
print("--- Definiowanie interfejsu Gradio ---")
|