File size: 10,259 Bytes
7931060 78f16cf 7931060 36a3e6b 7931060 36a3e6b 7931060 36a3e6b 7931060 36a3e6b 7931060 |
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 |
import gradio as gr
import requests # Dodajemy od razu, bo będzie potrzebny
from PIL import Image # Dodajemy od razu
import io # Dodajemy od razu
import os # Do odczytu sekretu
import traceback # Do lepszego śledzenia błędów
print("--- Plik app.py - Start ładowania ---")
# --- Konfiguracja ---
# Odczytanie sekretu (API Token) ustawionego w Space
# PAMIĘTAJ, ABY DODAĆ TEN SEKRET W USTAWIENIACH SPACE'A!
# Settings -> Repository secrets -> New secret
# Name: HUGGINGFACE_API_TOKEN
# Value: <twój skopiowany token API>
API_TOKEN = os.getenv("HUGGINGFACE_API_TOKEN")
# Sprawdzenie, czy token został załadowany
if not API_TOKEN:
print("!!! OSTRZEŻENIE: Nie znaleziono sekretu HUGGINGFACE_API_TOKEN !!!")
# Można tu rzucić błąd, ale na razie tylko ostrzegamy
# raise ValueError("Nie ustawiono sekretu HUGGINGFACE_API_TOKEN w ustawieniach Space!")
# Endpoint modelu InstantID na Hugging Face Inference API
API_URL = "https://api-inference.huggingface.co/models/InstantX/InstantID"
# Profesjonalny prompt, który chcemy uzyskać
# Możesz go później dostosować
LINKEDIN_PROMPT = (
"linkedin professional profile photo, corporate headshot, high quality, realistic photograph, "
"man/woman [choose one or remove] wearing a dark business suit or elegant blouse, plain white background, "
"soft studio lighting, sharp focus, looking at camera, slight smile, natural skin texture"
)
# Uwaga: InstantID może nie być idealny w zmianie ubrań, skupia się na twarzy.
# Prompt pomaga nadać ogólny styl.
# --- Logika aplikacji ---
# Na razie pusta funkcja, którą podłączymy do przycisku
def generate_photo(input_selfie, current_prompt): # Dodajemy prompt jako wejście
print("\n--- Funkcja generate_photo 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!")
if not current_prompt:
print("Błąd: Prompt jest pusty.")
raise gr.Error("Prompt nie może być pusty!")
if not API_TOKEN:
print("KRYTYCZNY BŁĄD: Brak API Tokena!")
raise gr.Error("Błąd konfiguracji serwera: Brak API Tokena. Skontaktuj się z administratorem.")
print(f"Otrzymano obrazek typu: {type(input_selfie)}, Rozmiar: {input_selfie.size}")
print(f"Otrzymano prompt (początek): {current_prompt[:100]}...")
print("Przygotowuję dane do wysłania do API...")
# 2. Przygotowanie danych do wysłania (Obrazek jako bajty)
try:
buffered = io.BytesIO()
# Sprawdźmy format i ewentualnie skonwertujmy tło dla PNG z przezroczystością
image_format = "JPEG" # Preferowany format dla wielu API
processed_image = input_selfie
if input_selfie.mode == 'RGBA':
print("Konwertuję obraz RGBA na RGB z białym tłem...")
processed_image = Image.new("RGB", input_selfie.size, (255, 255, 255))
processed_image.paste(input_selfie, mask=input_selfie.split()[3]) # Wklej z maską alfa
processed_image.save(buffered, format=image_format)
buffered.seek(0)
print(f"Obrazek przekonwertowany do formatu {image_format} i zapisany w buforze.")
except Exception as e:
print(f"BŁĄD podczas przetwarzania obrazu: {e}")
traceback.print_exc() # Wydrukuj pełny ślad błędu w logach
raise gr.Error(f"Wystąpił błąd podczas przetwarzania Twojego zdjęcia: {e}")
# 3. Przygotowanie nagłówków i danych dla API
headers = {
"Authorization": f"Bearer {API_TOKEN}"
# Content-Type zostanie ustawiony automatycznie przez requests dla multipart/form-data
}
# InstantID (i wiele modeli image-to-image w Inference API) oczekuje
# obrazu w 'files' i parametrów (jak prompt) w 'data'.
# WAŻNE: Nazwa klucza dla obrazu ('image') i promptu ('prompt')
# MUSI odpowiadać temu, czego oczekuje API modelu Sxela/instant-id.
# Sprawdź dokumentację modelu na HF, jeśli to nie zadziała!
files = {
'image': ('input_selfie.jpg', buffered, f'image/{image_format.lower()}')
}
payload = {
'prompt': current_prompt
# Możesz tu dodać inne parametry wspierane przez model, np.:
# 'negative_prompt': 'cartoon, drawing, illustration, sketch, duplicate heads, multiple people',
# 'num_inference_steps': 30,
# 'guidance_scale': 5.0,
}
print(f"Wysyłam zapytanie POST do: {API_URL}")
print(f"Payload (parametry tekstowe): {payload}")
# Nie drukujemy 'files', bo zawiera dane binarne
# 4. Wywołanie API i obsługa odpowiedzi
try:
# Zwiększamy timeout, bo generowanie może chwilę potrwać
response = requests.post(API_URL, headers=headers, data=payload, files=files, timeout=180) # 3 minuty timeout
print(f"Otrzymano odpowiedź od API. Status: {response.status_code}")
# Zawsze sprawdzaj content-type odpowiedzi!
content_type = response.headers.get('Content-Type', '')
print(f"Content-Type odpowiedzi: {content_type}")
# Rzuć wyjątek dla złych statusów (4xx, 5xx)
response.raise_for_status()
# Sprawdź, czy odpowiedź to na pewno obrazek
if 'image' not in content_type:
# Jeśli API zwróciło sukces (200), ale nie obrazek, to prawdopodobnie błąd w JSON
print(f"BŁĄD: API zwróciło status {response.status_code} ale nie obrazek (Content-Type: {content_type}). Treść:")
# Spróbuj wydrukować treść jako tekst (może zawierać komunikat błędu)
try:
print(response.text[:1000]) # Pokaż do 1000 znaków
raise gr.Error(f"API zwróciło nieoczekiwany format danych zamiast obrazka. Szczegóły: {response.text[:200]}")
except Exception as json_err:
print(f"Nie można odczytać treści odpowiedzi jako tekst: {json_err}")
raise gr.Error(f"API zwróciło nieoczekiwany format danych (Content-Type: {content_type}).")
# Jeśli status OK i Content-Type to obrazek, otwórz go
print("Próbuję otworzyć obrazek z odpowiedzi API...")
generated_image = Image.open(io.BytesIO(response.content))
print(f"Sukces! Otworzono wygenerowany obrazek. Rozmiar: {generated_image.size}")
# Zwróć wygenerowany obrazek do interfejsu Gradio
return generated_image # Zwracamy obiekt PIL
# Obsługa błędów z biblioteki requests (np. timeout, problem z połączeniem, błąd statusu z raise_for_status)
except requests.exceptions.RequestException as e:
print(f"BŁĄD podczas komunikacji z API: {e}")
# Spróbujmy wyświetlić odpowiedź błędu z API, jeśli jest dostępna
error_details = str(e)
if e.response is not None:
print(f"Odpowiedź błędu API (status {e.response.status_code}):")
try:
# Spróbuj zdekodować JSON jeśli to możliwe
error_json = e.response.json()
print(error_json)
error_details = f"API Error (status {e.response.status_code}): {error_json.get('error', e.response.text)}"
except ValueError: # Not JSON
error_text = e.response.text[:500] # Pokaż fragment tekstu błędu
print(error_text)
error_details = f"API Error (status {e.response.status_code}): {error_text}"
traceback.print_exc()
raise gr.Error(f"Problem z połączeniem lub błąd API: {error_details}")
# Obsługa innych, nieoczekiwanych błędów
except Exception as e:
print(f"Nieoczekiwany BŁĄD w funkcji generate_photo: {e}")
traceback.print_exc() # Wypisz pełny ślad błędu w logach
raise gr.Error(f"Wystąpił wewnętrzny błąd aplikacji: {e}")
# --- Budowa Interfejsu Gradio ---
print("--- Definiowanie interfejsu Gradio ---")
with gr.Blocks(css="footer {display: none !important}") as demo: # Ukrywamy domyślny footer Gradio
gr.Markdown(
"""
# Generator Profesjonalnych Zdjęć Profilowych
Wgraj swoje selfie, a my postaramy się stworzyć profesjonalne zdjęcie w stylu LinkedIn!
**Ważne:** Najlepsze efekty uzyskasz wgrywając wyraźne zdjęcie twarzy, patrzącej w miarę prosto, dobrze oświetlonej.
"""
)
with gr.Row():
with gr.Column(scale=1):
input_image = gr.Image(
label="1. Wgraj swoje selfie (JPG/PNG)",
type="pil" # Chcemy obiekt PIL w funkcji Pythona
)
prompt_input = gr.Textbox( # Dodajemy pole na prompt, z domyślną wartością
label="Opcjonalnie: Dostosuj opis zdjęcia (prompt)",
value=LINKEDIN_PROMPT,
lines=4
)
generate_button = gr.Button("✨ Generuj Zdjęcie Biznesowe ✨", variant="primary")
with gr.Column(scale=1):
output_image = gr.Image(
label="Oto Twoje wygenerowane zdjęcie:",
type="pil" # Oczekujemy obiektu PIL jako wynik
)
# Można dodać informację o stanie
status_info = gr.Textbox(label="Status", interactive=False)
# --- Podłączenie akcji do przycisku ---
# Kiedy przycisk zostanie kliknięty, wywołaj funkcję 'generate_photo'
# Przekaż zawartość 'input_image' jako argument do funkcji
# Wynik funkcji umieść w komponencie 'output_image'
generate_button.click(
fn=generate_photo,
inputs=[input_image, prompt_input], # Lista komponentów wejściowych
outputs=[output_image] # Lista komponentów wyjściowych (na razie jeden)
# inputs=[input_image, prompt_input], # Jeśli chcemy przekazać też prompt
# outputs=[output_image, status_info] # Jeśli chcemy aktualizować też status
)
print("--- Interfejs Gradio zdefiniowany ---")
# --- Uruchomienie aplikacji ---
if __name__ == "__main__":
print("--- Uruchamianie demo.launch() ---")
demo.launch() # Uruchamia aplikację Gradio
print("--- demo.launch() zakończone (?) ---") # Ten print może się nie pojawić od razu w logach |