|
import gradio as gr |
|
import requests |
|
from PIL import Image |
|
import io |
|
import os |
|
import traceback |
|
|
|
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 !!!") |
|
|
|
|
|
|
|
|
|
API_URL = "https://api-inference.huggingface.co/models/InstantX/InstantID" |
|
|
|
|
|
|
|
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" |
|
) |
|
|
|
|
|
|
|
|
|
|
|
def generate_photo(input_selfie, current_prompt): |
|
print("\n--- Funkcja generate_photo 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 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...") |
|
|
|
|
|
try: |
|
buffered = io.BytesIO() |
|
|
|
image_format = "JPEG" |
|
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]) |
|
|
|
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() |
|
raise gr.Error(f"Wystąpił błąd podczas przetwarzania Twojego zdjęcia: {e}") |
|
|
|
|
|
headers = { |
|
"Authorization": f"Bearer {API_TOKEN}" |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
files = { |
|
'image': ('input_selfie.jpg', buffered, f'image/{image_format.lower()}') |
|
} |
|
payload = { |
|
'prompt': current_prompt |
|
|
|
|
|
|
|
|
|
} |
|
|
|
print(f"Wysyłam zapytanie POST do: {API_URL}") |
|
print(f"Payload (parametry tekstowe): {payload}") |
|
|
|
|
|
|
|
try: |
|
|
|
response = requests.post(API_URL, headers=headers, data=payload, files=files, timeout=180) |
|
|
|
print(f"Otrzymano odpowiedź od API. Status: {response.status_code}") |
|
|
|
content_type = response.headers.get('Content-Type', '') |
|
print(f"Content-Type odpowiedzi: {content_type}") |
|
|
|
|
|
response.raise_for_status() |
|
|
|
|
|
if 'image' not in content_type: |
|
|
|
print(f"BŁĄD: API zwróciło status {response.status_code} ale nie obrazek (Content-Type: {content_type}). Treść:") |
|
|
|
try: |
|
print(response.text[:1000]) |
|
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}).") |
|
|
|
|
|
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}") |
|
|
|
|
|
return generated_image |
|
|
|
|
|
except requests.exceptions.RequestException as e: |
|
print(f"BŁĄD podczas komunikacji z API: {e}") |
|
|
|
error_details = str(e) |
|
if e.response is not None: |
|
print(f"Odpowiedź błędu API (status {e.response.status_code}):") |
|
try: |
|
|
|
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: |
|
error_text = e.response.text[:500] |
|
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}") |
|
|
|
|
|
except Exception as e: |
|
print(f"Nieoczekiwany BŁĄD w funkcji generate_photo: {e}") |
|
traceback.print_exc() |
|
raise gr.Error(f"Wystąpił wewnętrzny błąd aplikacji: {e}") |
|
|
|
|
|
|
|
print("--- Definiowanie interfejsu Gradio ---") |
|
with gr.Blocks(css="footer {display: none !important}") as demo: |
|
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" |
|
) |
|
prompt_input = gr.Textbox( |
|
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" |
|
) |
|
|
|
status_info = gr.Textbox(label="Status", interactive=False) |
|
|
|
|
|
|
|
|
|
|
|
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("--- demo.launch() zakończone (?) ---") |