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