File size: 10,498 Bytes
8f2ab11
1b06caa
7931060
8f2ab11
 
72f2543
 
 
ae2c112
8f2ab11
 
1b06caa
7931060
 
 
 
 
 
72f2543
 
 
7931060
 
 
8f2ab11
7931060
 
72f2543
 
7931060
1b06caa
7931060
e7d12ee
1b06caa
 
8f2ab11
1b06caa
 
 
 
 
 
 
 
aaf6a9f
 
 
 
8f2ab11
 
aaf6a9f
8f2ab11
1b06caa
aaf6a9f
 
 
 
 
 
 
8f2ab11
 
 
aaf6a9f
 
 
1b06caa
aaf6a9f
 
 
 
1b06caa
 
 
aaf6a9f
8f2ab11
aaf6a9f
 
1b06caa
 
aaf6a9f
1b06caa
8f2ab11
 
1b06caa
e7d12ee
8f2ab11
 
 
e7d12ee
b5409e4
 
8f2ab11
 
e7d12ee
1b06caa
 
72f2543
 
 
 
e7d12ee
72f2543
 
 
 
 
 
 
e7d12ee
8f2ab11
 
 
e7d12ee
ae2c112
8f2ab11
 
 
 
 
 
 
e7d12ee
 
 
8f2ab11
e7d12ee
 
1b06caa
72f2543
e7d12ee
72f2543
 
e7d12ee
72f2543
e7d12ee
72f2543
e7d12ee
 
 
36a3e6b
1b06caa
 
 
 
 
 
 
 
 
72f2543
1b06caa
 
 
 
 
 
 
72f2543
 
 
 
7931060
e7d12ee
8f2ab11
 
e7d12ee
 
 
 
 
 
8f2ab11
e7d12ee
72f2543
e7d12ee
 
1b06caa
72f2543
 
7931060
 
 
1b06caa
7931060
 
 
72f2543
 
 
 
1b06caa
7931060
 
 
 
8f2ab11
 
 
7931060
8f2ab11
7931060
 
8f2ab11
 
 
7931060
 
 
 
 
 
8f2ab11
 
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
212
213
214
# -*- coding: utf-8 -*-

import gradio as gr
# import requests # Nadal nieużywany bezpośrednio
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 ---")

# --- Konfiguracja ---
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} ---")


# --- Logika aplikacji ---
def generate_photo(input_selfie, current_prompt):
    print("\n--- Funkcja generate_photo (gradio_client) została wywołana ---")

    # 1. Walidacja
    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]}...")

    # 2. Skalowanie obrazka
    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}")

    # 3. Przygotowanie pliku tymczasowego
    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}")

    # 4. Wywołanie zdalnego API Gradio
    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)
        # Usunięto odwołanie do client.space_host, zastąpiono prostszym printem:
        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}")

        # UWAGA: file() jest przestarzałe. Jeśli pojawią się błędy, spróbuj Client(..., serialize=False)
        # i przekazuj pełną ścieżkę jako string: input_image_path zamiast file(input_image_path)
        # Ale na razie zostawiamy file()
        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:
        # 5. Sprzątanie
        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}")

    # 6. Zwróć wynik
    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.")


# --- Budowa Interfejsu Gradio ---
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 ---")

# --- Uruchomienie aplikacji ---
if __name__ == "__main__":
    print("--- Uruchamianie demo.launch() ---")
    demo.launch()
    print("--- Aplikacja Gradio zakończyła działanie ---")