|
|
|
|
|
import gradio as gr |
|
|
|
from PIL import Image, ImageOps |
|
import io |
|
import os |
|
import traceback |
|
from gradio_client import Client, handle_file |
|
import uuid |
|
import shutil |
|
from gradio_client.exceptions import AppError |
|
|
|
print("--- Plik app.py - Start ładowania ---") |
|
|
|
|
|
API_TOKEN = os.getenv("HUGGINGFACE_API_TOKEN") |
|
if not API_TOKEN: |
|
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. ---") |
|
|
|
LINKEDIN_PROMPT = ( |
|
"linkedin professional profile photo, corporate headshot, high quality, realistic photograph, " |
|
"person wearing a dark business suit or elegant blouse, plain white background, " |
|
"soft studio lighting, sharp focus, looking at camera, slight smile, natural skin texture" |
|
) |
|
TARGET_SPACE_ID = "InstantX/InstantID" |
|
print(f"--- Konfiguracja załadowana. Cel dla gradio_client: {TARGET_SPACE_ID} ---") |
|
|
|
|
|
|
|
def generate_photo(input_selfie, current_prompt): |
|
print("\n--- Funkcja generate_photo (gradio_client) została wywołana ---") |
|
|
|
|
|
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 (opis zdjęcia) nie może być pusty!") |
|
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)}, Oryginalny rozmiar: {input_selfie.size}") |
|
print(f"Otrzymano prompt (początek): {current_prompt[:100]}...") |
|
|
|
|
|
input_selfie_resized = None |
|
try: |
|
max_size = (1024, 1024) |
|
input_selfie_resized = input_selfie.copy() |
|
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}") |
|
|
|
|
|
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") |
|
rgb_image = input_selfie_resized |
|
if rgb_image.mode == 'RGBA': |
|
print("Konwertuję przeskalowany obraz RGBA na RGB z białym tłem...") |
|
final_image = Image.new("RGB", rgb_image.size, (255, 255, 255)) |
|
final_image.paste(rgb_image, mask=rgb_image.split()[3]) |
|
elif rgb_image.mode != 'RGB': |
|
print(f"Konwertuję przeskalowany obraz z trybu {rgb_image.mode} na RGB...") |
|
final_image = rgb_image.convert('RGB') |
|
else: |
|
final_image = rgb_image |
|
final_image.save(input_image_path, format="JPEG") |
|
print(f"Przeskalowany obrazek wejściowy zapisany tymczasowo w: {input_image_path}") |
|
except Exception as e_save: |
|
print(f"BŁĄD podczas zapisywania obrazu tymczasowego: {e_save}") |
|
traceback.print_exc() |
|
if os.path.exists(temp_dir): |
|
try: shutil.rmtree(temp_dir) |
|
except Exception as e_clean: print(f"OSTRZEŻENIE: Nie udało się usunąć {temp_dir}: {e_clean}") |
|
raise gr.Error(f"Problem z przygotowaniem obrazu do wysłania: {e_save}") |
|
|
|
|
|
output_image = None |
|
client = None |
|
try: |
|
print(f"Łączenie z docelowym Space Gradio: {TARGET_SPACE_ID}") |
|
client = Client(TARGET_SPACE_ID, hf_token=API_TOKEN) |
|
|
|
print(f"Połączono z {TARGET_SPACE_ID}. Klient zainicjalizowany.") |
|
|
|
print("Próbuję wywołać funkcję na zdalnym Space...") |
|
negative_prompt = "ugly, deformed, noisy, blurry, low contrast, text, signature, watermark, duplicate, multiple people, cartoon, drawing, illustration, sketch, bad anatomy, worst quality, low quality" |
|
style_name = "Realistic" |
|
cn_scale = 0.8 |
|
ip_scale = 0.8 |
|
api_endpoint_name = "/generate_image" |
|
|
|
print(f"Wywołuję endpoint '{api_endpoint_name}' z parametrami:") |
|
print(f" Input image path: {input_image_path}") |
|
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}") |
|
|
|
|
|
|
|
|
|
result = client.predict( |
|
handle_file(input_image_path), |
|
None, |
|
current_prompt, |
|
negative_prompt, |
|
style_name, |
|
cn_scale, |
|
ip_scale, |
|
api_name=api_endpoint_name |
|
) |
|
|
|
print(f"Otrzymano wynik od klienta: {type(result)}") |
|
print(f"Wynik (fragment): {str(result)[:500]}") |
|
|
|
if isinstance(result, list) and len(result) > 0 and isinstance(result[0], str) and os.path.exists(result[0]): |
|
output_file_path = result[0] |
|
print(f"Przetwarzam pierwszy 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}") |
|
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: |
|
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 AppError as e: |
|
print(f"BŁĄD APLIKACJI ZDALNEJ (AppError): {e}") |
|
traceback.print_exc() |
|
error_message = f"Zdalny serwis ({TARGET_SPACE_ID}) zgłosił wewnętrzny błąd. Może to być spowodowane problemami z obrazkiem (np. brak wykrytej twarzy, nietypowy format), niedostępnością modelu lub przeciążeniem serwisu. Spróbuj z innym, wyraźnym zdjęciem twarzy lub spróbuj ponownie później." |
|
raise gr.Error(error_message) |
|
except ValueError as e: |
|
print(f"BŁĄD WARTOŚCI (ValueError): {e}") |
|
traceback.print_exc() |
|
error_message = f"Wystąpił błąd wartości: {e}. " |
|
if "Could not fetch config" in str(e): |
|
error_message = f"Nie można pobrać konfiguracji zdalnego serwisu ({TARGET_SPACE_ID}). Może być chwilowo niedostępny, niekompatybilny lub wymagać logowania. Sprawdź status serwisu lub spróbuj później." |
|
raise gr.Error(error_message) |
|
except Exception as e: |
|
print(f"NIEOCZEKIWANY BŁĄD: {e}") |
|
traceback.print_exc() |
|
error_message = f"Wystąpił nieoczekiwany błąd: {e}. Sprawdź logi aplikacji." |
|
if "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ę." |
|
raise gr.Error(error_message) |
|
|
|
finally: |
|
|
|
if input_image_path and os.path.exists(temp_dir): |
|
try: |
|
shutil.rmtree(temp_dir) |
|
print(f"Folder tymczasowy {temp_dir} usunięty.") |
|
except Exception as e_clean: |
|
print(f"OSTRZEŻENIE: Nie udało się usunąć folderu tymczasowego {temp_dir}: {e_clean}") |
|
|
|
|
|
if output_image: |
|
print("Zwracam wygenerowany obrazek do interfejsu Gradio.") |
|
return output_image |
|
else: |
|
print("BŁĄD KRYTYCZNY: Brak obrazka wynikowego, a nie zgłoszono błędu.") |
|
raise gr.Error("Nie udało się uzyskać obrazka wynikowego z nieznanego powodu.") |
|
|
|
|
|
|
|
print("--- Definiowanie interfejsu Gradio ---") |
|
with gr.Blocks(css="footer {display: none !important;}", title="Generator Zdjęć Biznesowych") as demo: |
|
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** (zwłaszcza za pierwszym razem). Bądź cierpliwy! |
|
""" |
|
) |
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
input_image = gr.Image(label="1. Wgraj swoje selfie (JPG/PNG)", type="pil", sources=["upload", "webcam"]) |
|
prompt_input = gr.Textbox(label="2. Opis pożądanego zdjęcia (prompt)", value=LINKEDIN_PROMPT, lines=4) |
|
generate_button = gr.Button("✨ Generuj Zdjęcie Biznesowe ✨", variant="primary", scale=1) |
|
with gr.Column(scale=1): |
|
output_image = gr.Image(label="Oto Twoje wygenerowane zdjęcie:", type="pil") |
|
|
|
generate_button.click( |
|
fn=generate_photo, |
|
inputs=[input_image, prompt_input], |
|
outputs=[output_image] |
|
) |
|
print("--- Interfejs Gradio zdefiniowany ---") |
|
|
|
|
|
if __name__ == "__main__": |
|
print("--- Uruchamianie demo.launch() ---") |
|
demo.launch() |
|
print("--- Aplikacja Gradio zakończyła działanie ---") |