PaulinaAa's picture
Update app.py
78f16cf verified
raw
history blame
10.3 kB
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